From 7c31cbf9aec76c1ed378c7340808746e119b88cf Mon Sep 17 00:00:00 2001 From: Tom Osika Date: Thu, 21 May 2026 16:31:36 -0400 Subject: [PATCH 1/4] SBOM: Handle PACKAGE_URL argument --- Source/cmExportSbomGenerator.cxx | 1 + Source/cmSbomArguments.h | 2 ++ .../ProjectMetadata-install-check.cmake | 1 + .../RunCMake/ExportSbom/ProjectMetadata.cmake | 1 + Tests/RunCMake/ExportSbom/RunCMakeTest.cmake | 1 + .../ProjectMetadata-install-check.cmake | 1 + .../InstallSbom/ProjectMetadata.cmake | 1 + Tests/RunCMake/InstallSbom/RunCMakeTest.cmake | 1 + .../ProjectMetadataExplicitAssertions.cmake | 21 +++++++++++++++++++ 9 files changed, 30 insertions(+) create mode 100644 Tests/RunCMake/Sbom/ProjectMetadataExplicitAssertions.cmake diff --git a/Source/cmExportSbomGenerator.cxx b/Source/cmExportSbomGenerator.cxx index 53bdcef972..6b29404a74 100644 --- a/Source/cmExportSbomGenerator.cxx +++ b/Source/cmExportSbomGenerator.cxx @@ -56,6 +56,7 @@ cmExportSbomGenerator::cmExportSbomGenerator(cmSbomArguments args) , PackageDescription(std::move(args.Description)) , PackageWebsite(std::move(args.Website)) , PackageLicense(std::move(args.License)) + , PackageUrl(std::move(args.PackageUrl)) , PackageFormat(args.GetFormat()) { } diff --git a/Source/cmSbomArguments.h b/Source/cmSbomArguments.h index aafa84feed..17bb3b59c7 100644 --- a/Source/cmSbomArguments.h +++ b/Source/cmSbomArguments.h @@ -41,6 +41,7 @@ public: SbomFormat GetFormat() const; ArgumentParser::NonEmpty Format; + ArgumentParser::NonEmpty PackageUrl; protected: cm::string_view CommandName() const override; @@ -54,6 +55,7 @@ private: cmProjectInfoArguments* const base = self; Bind(base, parser, "SBOM"_s, &cmProjectInfoArguments::PackageName); Bind(self, parser, "FORMAT"_s, &cmSbomArguments::Format); + Bind(self, parser, "PACKAGE_URL"_s, &cmSbomArguments::PackageUrl); cmProjectInfoArguments::Bind(parser, self); } }; diff --git a/Tests/RunCMake/ExportSbom/ProjectMetadata-install-check.cmake b/Tests/RunCMake/ExportSbom/ProjectMetadata-install-check.cmake index 34e57af218..502cd43b56 100644 --- a/Tests/RunCMake/ExportSbom/ProjectMetadata-install-check.cmake +++ b/Tests/RunCMake/ExportSbom/ProjectMetadata-install-check.cmake @@ -1,2 +1,3 @@ file(READ "${RunCMake_TEST_BINARY_DIR}/sbom/test_targets/test_targets.spdx.json" content) include(${CMAKE_CURRENT_LIST_DIR}/../Sbom/ProjectMetadata-install-check.cmake) +include(${CMAKE_CURRENT_LIST_DIR}/../Sbom/ProjectMetadataExplicitAssertions.cmake) diff --git a/Tests/RunCMake/ExportSbom/ProjectMetadata.cmake b/Tests/RunCMake/ExportSbom/ProjectMetadata.cmake index ed96780277..6aaa68bf66 100644 --- a/Tests/RunCMake/ExportSbom/ProjectMetadata.cmake +++ b/Tests/RunCMake/ExportSbom/ProjectMetadata.cmake @@ -6,5 +6,6 @@ export( DESCRIPTION "An eloquent description" LICENSE "BSD-3" HOMEPAGE_URL "www.example.com" + PACKAGE_URL "https://example.com/test_targets.tar.gz" VERSION "1.3.4" ) diff --git a/Tests/RunCMake/ExportSbom/RunCMakeTest.cmake b/Tests/RunCMake/ExportSbom/RunCMakeTest.cmake index 3447c2e16c..4956d44c27 100644 --- a/Tests/RunCMake/ExportSbom/RunCMakeTest.cmake +++ b/Tests/RunCMake/ExportSbom/RunCMakeTest.cmake @@ -28,3 +28,4 @@ run_cmake_install(Requirements) run_cmake_install(MissingPackageNamespace) run_cmake_install(ReferencesNonExportedTarget) +run_cmake_install(ProjectMetadata) diff --git a/Tests/RunCMake/InstallSbom/ProjectMetadata-install-check.cmake b/Tests/RunCMake/InstallSbom/ProjectMetadata-install-check.cmake index 355e80d184..75adf4ead0 100644 --- a/Tests/RunCMake/InstallSbom/ProjectMetadata-install-check.cmake +++ b/Tests/RunCMake/InstallSbom/ProjectMetadata-install-check.cmake @@ -1,2 +1,3 @@ file(READ "${RunCMake_TEST_INSTALL_DIR}/test_targets.spdx.json" content) include(${CMAKE_CURRENT_LIST_DIR}/../Sbom/ProjectMetadata-install-check.cmake) +include(${CMAKE_CURRENT_LIST_DIR}/../Sbom/ProjectMetadataExplicitAssertions.cmake) diff --git a/Tests/RunCMake/InstallSbom/ProjectMetadata.cmake b/Tests/RunCMake/InstallSbom/ProjectMetadata.cmake index a743217705..db94319adf 100644 --- a/Tests/RunCMake/InstallSbom/ProjectMetadata.cmake +++ b/Tests/RunCMake/InstallSbom/ProjectMetadata.cmake @@ -5,6 +5,7 @@ install( DESCRIPTION "An eloquent description" LICENSE "BSD-3" HOMEPAGE_URL "www.example.com" + PACKAGE_URL "https://example.com/test_targets.tar.gz" VERSION "1.3.4" EXPORT test_targets DESTINATION . diff --git a/Tests/RunCMake/InstallSbom/RunCMakeTest.cmake b/Tests/RunCMake/InstallSbom/RunCMakeTest.cmake index 1a7a9e96c0..895cc2622e 100644 --- a/Tests/RunCMake/InstallSbom/RunCMakeTest.cmake +++ b/Tests/RunCMake/InstallSbom/RunCMakeTest.cmake @@ -29,3 +29,4 @@ run_cmake_install(Requirements) run_cmake_install(IgnoresInterfaceDirs) run_cmake_install(MissingPackageNamespace) run_cmake_install(ReferencesNonExportedTarget) +run_cmake_install(ProjectMetadata) diff --git a/Tests/RunCMake/Sbom/ProjectMetadataExplicitAssertions.cmake b/Tests/RunCMake/Sbom/ProjectMetadataExplicitAssertions.cmake new file mode 100644 index 0000000000..33fca83d9e --- /dev/null +++ b/Tests/RunCMake/Sbom/ProjectMetadataExplicitAssertions.cmake @@ -0,0 +1,21 @@ +include(${CMAKE_CURRENT_LIST_DIR}/Assertions.cmake) + +set(DOCUMENT_METADATA_EXPECTED [=[ +{ + "description" : "An eloquent description", + "dataLicense" : "BSD-3" +} +]=]) + +set(PACKAGE_METADATA_EXPECTED [=[ +{ + "spdxId" : "urn:test#Package", + "software_packageVersion" : "1.3.4", + "software_homePage" : "www.example.com", + "software_downloadLocation" : "https://example.com/test_targets.tar.gz" +} +]=]) + +string(JSON SPDX_DOCUMENT GET "${content}" "@graph" "1") +expect_object("${SPDX_DOCUMENT}" DOCUMENT_METADATA_EXPECTED) +expect_object("${SPDX_DOCUMENT}" PACKAGE_METADATA_EXPECTED "rootElement") From 966982957364363bb3e3835e2d121e16274aefa6 Mon Sep 17 00:00:00 2001 From: Tom Osika Date: Sun, 24 May 2026 18:28:07 -0400 Subject: [PATCH 2/4] Install: Refactor ResolveTargetsInGeneratorExpression --- Source/cmExportFileGenerator.cxx | 36 ++++++++++++++++++++++---------- Source/cmExportFileGenerator.h | 15 +++++++++++++ 2 files changed, 40 insertions(+), 11 deletions(-) diff --git a/Source/cmExportFileGenerator.cxx b/Source/cmExportFileGenerator.cxx index 9d8311d6d7..353aff7302 100644 --- a/Source/cmExportFileGenerator.cxx +++ b/Source/cmExportFileGenerator.cxx @@ -4,10 +4,12 @@ #include #include +#include #include #include #include +#include #include #include @@ -449,9 +451,9 @@ void cmExportFileGenerator::ResolveTargetsInGeneratorExpressions( } } -void cmExportFileGenerator::ResolveTargetsInGeneratorExpression( - std::string& input, cmGeneratorTarget const* target, - cmLocalGenerator const* lg) +cm::optional cmResolveTargetsInGeneratorExpression( + std::string& input, + std::function const& addTargetNamespace) { std::string::size_type pos = 0; std::string::size_type lastPos = pos; @@ -474,7 +476,7 @@ void cmExportFileGenerator::ResolveTargetsInGeneratorExpression( std::string targetName = input.substr(nameStartPos, commaPos - nameStartPos); - if (this->AddTargetNamespace(targetName, target, lg)) { + if (addTargetNamespace(targetName)) { input.replace(nameStartPos, commaPos - nameStartPos, targetName); } lastPos = nameStartPos + targetName.size() + 1; @@ -496,7 +498,7 @@ void cmExportFileGenerator::ResolveTargetsInGeneratorExpression( "literal."; break; } - if (!this->AddTargetNamespace(targetName, target, lg)) { + if (!addTargetNamespace(targetName)) { errorString = "$ requires its parameter to be a " "reachable target."; break; @@ -517,7 +519,7 @@ void cmExportFileGenerator::ResolveTargetsInGeneratorExpression( } std::string libName = input.substr(nameStartPos, endPos - nameStartPos); if (cmGeneratorExpression::IsValidTargetName(libName) && - this->AddTargetNamespace(libName, target, lg)) { + addTargetNamespace(libName)) { input.replace(nameStartPos, endPos - nameStartPos, libName); } lastPos = nameStartPos + libName.size() + 1; @@ -533,17 +535,29 @@ void cmExportFileGenerator::ResolveTargetsInGeneratorExpression( } std::string libName = input.substr(nameStartPos, endPos - nameStartPos); if (cmGeneratorExpression::IsValidTargetName(libName) && - this->AddTargetNamespace(libName, target, lg)) { + addTargetNamespace(libName)) { input.replace(nameStartPos, endPos - nameStartPos, libName); } lastPos = nameStartPos + libName.size() + 1; } - this->ReplaceInstallPrefix(input); - if (!errorString.empty()) { - target->GetLocalGenerator()->IssueMessage(MessageType::FATAL_ERROR, - errorString); + return errorString; + } + return cm::nullopt; +} + +void cmExportFileGenerator::ResolveTargetsInGeneratorExpression( + std::string& input, cmGeneratorTarget const* target, + cmLocalGenerator const* lg) +{ + auto err = cmResolveTargetsInGeneratorExpression( + input, [this, target, lg](std::string& name) { + return this->AddTargetNamespace(name, target, lg); + }); + this->ReplaceInstallPrefix(input); + if (err) { + target->GetLocalGenerator()->IssueMessage(MessageType::FATAL_ERROR, *err); } } diff --git a/Source/cmExportFileGenerator.h b/Source/cmExportFileGenerator.h index 814fd544e7..07ebe80b29 100644 --- a/Source/cmExportFileGenerator.h +++ b/Source/cmExportFileGenerator.h @@ -4,12 +4,14 @@ #include "cmConfigure.h" // IWYU pragma: keep +#include #include #include #include #include #include +#include #include #include "cmDiagnostics.h" @@ -238,3 +240,16 @@ extern template void cmExportFileGenerator::SetImportLinkProperty( std::string const&, cmGeneratorTarget const*, std::string const&, std::vector const&, ImportPropertyMap& properties, ImportLinkPropertyTargetNames); + +/** Walk a generator expression and rewrite the target-name slot of each + `$`, `$`, `$`, and + `$` construct via the provided callback. The callback + receives the bare target name; if it mutates the name and returns true, + the helper splices the new name back into the genex. Returns the + parse-level error message, if an error was encountered. Callers handle + install-prefix substitution and error reporting themselves. This is used + by the cmExportFileGenerator hierarchy, as well as the cmSbomBuilder + hierarchy. */ +cm::optional cmResolveTargetsInGeneratorExpression( + std::string& input, + std::function const& addTargetNamespace); From 273fc4cc49b993d643d79385025e51b42f6ac988 Mon Sep 17 00:00:00 2001 From: Tom Osika Date: Mon, 13 Apr 2026 21:29:06 -0400 Subject: [PATCH 3/4] SBOM: Support multi export set SBOMs --- Help/command/export.rst | 9 +- Help/command/install.rst | 10 +- Source/CMakeLists.txt | 18 +- Source/cmBuildSbomBuilder.cxx | 41 +++ Source/cmBuildSbomBuilder.h | 32 ++ Source/cmBuildSbomGenerator.cxx | 16 + Source/cmBuildSbomGenerator.h | 59 ++++ Source/cmExportBuildFileGenerator.cxx | 45 ++- Source/cmExportBuildFileGenerator.h | 13 + Source/cmExportBuildSbomGenerator.cxx | 89 ------ Source/cmExportBuildSbomGenerator.h | 41 --- Source/cmExportCommand.cxx | 109 +++++-- Source/cmExportFileGenerator.cxx | 11 +- Source/cmExportFileGenerator.h | 14 +- Source/cmExportInstallFileGenerator.cxx | 42 +-- Source/cmExportInstallSbomGenerator.cxx | 247 ---------------- Source/cmExportInstallSbomGenerator.h | 69 ----- Source/cmExportSbomGenerator.h | 86 ------ Source/cmGlobalGenerator.cxx | 154 +++++++++- Source/cmGlobalGenerator.h | 51 ++++ Source/cmInstallCommand.cxx | 49 +-- Source/cmInstallSbomBuilder.cxx | 42 +++ Source/cmInstallSbomBuilder.h | 32 ++ Source/cmInstallSbomExportGenerator.cxx | 30 -- Source/cmInstallSbomExportGenerator.h | 31 -- Source/cmInstallSbomGenerator.cxx | 88 ++++++ Source/cmInstallSbomGenerator.h | 68 +++++ Source/cmMakefile.cxx | 27 +- Source/cmMakefile.h | 15 +- ...ortSbomGenerator.cxx => cmSbomBuilder.cxx} | 278 ++++++++++++++++-- Source/cmSbomBuilder.h | 179 +++++++++++ .../PartialCoverage-install-check.cmake | 24 ++ Tests/RunCMake/EnvSbom/PartialCoverage.cmake | 12 + .../PartialCoverage_subdir/CMakeLists.txt | 7 + ...encesNonExportedTarget-install-check.cmake | 2 - .../ReferencesNonExportedTarget-result.txt | 1 + .../ReferencesNonExportedTarget-stderr.txt | 6 + Tests/RunCMake/EnvSbom/RunCMakeTest.cmake | 15 +- .../ExportSbom/ApplicationTarget.cmake | 2 +- .../ExportSbom/DuplicateSbom-result.txt | 1 + .../ExportSbom/DuplicateSbom-stderr.txt | 2 + Tests/RunCMake/ExportSbom/DuplicateSbom.cmake | 5 + ...EmptyNamespaceFallback-install-check.cmake | 2 + .../EmptyNamespaceFallback-stderr.txt | 4 + .../ExportSbom/EmptyNamespaceFallback.cmake | 5 + .../InstallExportPlusSbomSameSet.cmake | 5 + .../RunCMake/ExportSbom/InterfaceTarget.cmake | 2 +- .../ExportSbom/MissingPackageNamespace.cmake | 2 +- .../MultiNamespaceAmbiguity-result.txt | 1 + .../MultiNamespaceAmbiguity-stderr.txt | 7 + .../ExportSbom/MultiNamespaceAmbiguity.cmake | 5 + .../ExportSbom/MultiSetAmbiguity-result.txt | 1 + .../ExportSbom/MultiSetAmbiguity-stderr.txt | 8 + .../ExportSbom/MultiSetAmbiguity.cmake | 5 + .../MultiSetSingleSbom-install-check.cmake | 2 + .../ExportSbom/MultiSetSingleSbom.cmake | 5 + .../RunCMake/ExportSbom/ProjectMetadata.cmake | 2 +- ...encesNonExportedTarget-install-check.cmake | 2 - .../ReferencesNonExportedTarget-result.txt | 1 + .../ReferencesNonExportedTarget-stderr.txt | 6 + .../ReferencesNonExportedTarget.cmake | 2 +- .../Requirements-install-check.cmake | 4 +- Tests/RunCMake/ExportSbom/Requirements.cmake | 5 +- Tests/RunCMake/ExportSbom/RunCMakeTest.cmake | 23 +- ...SbomNamespaceAmbiguity-install-check.cmake | 2 + .../SbomNamespaceAmbiguity-stderr.txt | 4 + .../ExportSbom/SbomNamespaceAmbiguity.cmake | 5 + .../SbomNamespaceFallback-install-check.cmake | 3 + .../ExportSbom/SbomNamespaceFallback.cmake | 4 + Tests/RunCMake/ExportSbom/SharedTarget.cmake | 2 +- .../TargetInMultipleSets-install-check.cmake | 2 + .../ExportSbom/TargetInMultipleSets.cmake | 3 + .../InstallSbom/ApplicationTarget.cmake | 2 +- .../InstallSbom/DuplicateSbom-result.txt | 1 + .../InstallSbom/DuplicateSbom-stderr.txt | 2 + .../RunCMake/InstallSbom/DuplicateSbom.cmake | 4 + ...EmptyNamespaceFallback-install-check.cmake | 2 + .../EmptyNamespaceFallback-stderr.txt | 4 + .../InstallSbom/EmptyNamespaceFallback.cmake | 5 + .../InstallSbom/IgnoresInterfaceDirs.cmake | 2 +- .../InstallExportPlusSbomSameSet.cmake | 5 + .../InstallSbom/InterfaceTarget.cmake | 2 +- .../InstallSbom/MissingPackageNamespace.cmake | 2 +- .../MultiNamespaceAmbiguity-result.txt | 1 + .../MultiNamespaceAmbiguity-stderr.txt | 8 + .../InstallSbom/MultiNamespaceAmbiguity.cmake | 5 + .../InstallSbom/MultiSetAmbiguity-result.txt | 1 + .../InstallSbom/MultiSetAmbiguity-stderr.txt | 7 + .../InstallSbom/MultiSetAmbiguity.cmake | 5 + .../MultiSetSingleSbom-install-check.cmake | 2 + .../InstallSbom/MultiSetSingleSbom.cmake | 5 + .../InstallSbom/ProjectMetadata.cmake | 2 +- ...encesNonExportedTarget-install-check.cmake | 2 - .../ReferencesNonExportedTarget-result.txt | 1 + .../ReferencesNonExportedTarget-stderr.txt | 6 + .../ReferencesNonExportedTarget.cmake | 2 +- .../Requirements-install-check.cmake | 4 +- Tests/RunCMake/InstallSbom/Requirements.cmake | 3 - Tests/RunCMake/InstallSbom/RunCMakeTest.cmake | 23 +- ...SbomNamespaceAmbiguity-install-check.cmake | 2 + .../SbomNamespaceAmbiguity-stderr.txt | 4 + .../InstallSbom/SbomNamespaceAmbiguity.cmake | 5 + .../SbomNamespaceFallback-install-check.cmake | 3 + .../InstallSbom/SbomNamespaceFallback.cmake | 4 + Tests/RunCMake/InstallSbom/SharedTarget.cmake | 2 +- .../TargetInMultipleSets-install-check.cmake | 2 + .../InstallSbom/TargetInMultipleSets.cmake | 3 + Tests/RunCMake/Sbom/DuplicateSbom.cmake | 6 + ...EmptyNamespaceFallback-install-check.cmake | 48 +++ .../Sbom/EmptyNamespaceFallback.cmake | 9 + .../Sbom/InstallExportPlusSbomSameSet.cmake | 12 + .../Sbom/MultiNamespaceAmbiguity.cmake | 9 + Tests/RunCMake/Sbom/MultiSetAmbiguity.cmake | 10 + .../MultiSetSingleSbom-install-check.cmake | 49 +++ Tests/RunCMake/Sbom/MultiSetSingleSbom.cmake | 9 + ...encesNonExportedTarget-install-check.cmake | 72 ----- .../Sbom/Requirements-install-check.cmake | 16 +- Tests/RunCMake/Sbom/Requirements.cmake | 5 +- ...SbomNamespaceAmbiguity-install-check.cmake | 49 +++ .../Sbom/SbomNamespaceAmbiguity.cmake | 9 + .../SbomNamespaceFallback-install-check.cmake | 78 +++++ .../RunCMake/Sbom/SbomNamespaceFallback.cmake | 9 + .../TargetInMultipleSets-install-check.cmake | 24 ++ .../RunCMake/Sbom/TargetInMultipleSets.cmake | 7 + 124 files changed, 1856 insertions(+), 894 deletions(-) create mode 100644 Source/cmBuildSbomBuilder.cxx create mode 100644 Source/cmBuildSbomBuilder.h create mode 100644 Source/cmBuildSbomGenerator.cxx create mode 100644 Source/cmBuildSbomGenerator.h delete mode 100644 Source/cmExportBuildSbomGenerator.cxx delete mode 100644 Source/cmExportBuildSbomGenerator.h delete mode 100644 Source/cmExportInstallSbomGenerator.cxx delete mode 100644 Source/cmExportInstallSbomGenerator.h delete mode 100644 Source/cmExportSbomGenerator.h create mode 100644 Source/cmInstallSbomBuilder.cxx create mode 100644 Source/cmInstallSbomBuilder.h delete mode 100644 Source/cmInstallSbomExportGenerator.cxx delete mode 100644 Source/cmInstallSbomExportGenerator.h create mode 100644 Source/cmInstallSbomGenerator.cxx create mode 100644 Source/cmInstallSbomGenerator.h rename Source/{cmExportSbomGenerator.cxx => cmSbomBuilder.cxx} (55%) create mode 100644 Source/cmSbomBuilder.h create mode 100644 Tests/RunCMake/EnvSbom/PartialCoverage-install-check.cmake create mode 100644 Tests/RunCMake/EnvSbom/PartialCoverage.cmake create mode 100644 Tests/RunCMake/EnvSbom/PartialCoverage_subdir/CMakeLists.txt delete mode 100644 Tests/RunCMake/EnvSbom/ReferencesNonExportedTarget-install-check.cmake create mode 100644 Tests/RunCMake/EnvSbom/ReferencesNonExportedTarget-result.txt create mode 100644 Tests/RunCMake/EnvSbom/ReferencesNonExportedTarget-stderr.txt create mode 100644 Tests/RunCMake/ExportSbom/DuplicateSbom-result.txt create mode 100644 Tests/RunCMake/ExportSbom/DuplicateSbom-stderr.txt create mode 100644 Tests/RunCMake/ExportSbom/DuplicateSbom.cmake create mode 100644 Tests/RunCMake/ExportSbom/EmptyNamespaceFallback-install-check.cmake create mode 100644 Tests/RunCMake/ExportSbom/EmptyNamespaceFallback-stderr.txt create mode 100644 Tests/RunCMake/ExportSbom/EmptyNamespaceFallback.cmake create mode 100644 Tests/RunCMake/ExportSbom/InstallExportPlusSbomSameSet.cmake create mode 100644 Tests/RunCMake/ExportSbom/MultiNamespaceAmbiguity-result.txt create mode 100644 Tests/RunCMake/ExportSbom/MultiNamespaceAmbiguity-stderr.txt create mode 100644 Tests/RunCMake/ExportSbom/MultiNamespaceAmbiguity.cmake create mode 100644 Tests/RunCMake/ExportSbom/MultiSetAmbiguity-result.txt create mode 100644 Tests/RunCMake/ExportSbom/MultiSetAmbiguity-stderr.txt create mode 100644 Tests/RunCMake/ExportSbom/MultiSetAmbiguity.cmake create mode 100644 Tests/RunCMake/ExportSbom/MultiSetSingleSbom-install-check.cmake create mode 100644 Tests/RunCMake/ExportSbom/MultiSetSingleSbom.cmake delete mode 100644 Tests/RunCMake/ExportSbom/ReferencesNonExportedTarget-install-check.cmake create mode 100644 Tests/RunCMake/ExportSbom/ReferencesNonExportedTarget-result.txt create mode 100644 Tests/RunCMake/ExportSbom/ReferencesNonExportedTarget-stderr.txt create mode 100644 Tests/RunCMake/ExportSbom/SbomNamespaceAmbiguity-install-check.cmake create mode 100644 Tests/RunCMake/ExportSbom/SbomNamespaceAmbiguity-stderr.txt create mode 100644 Tests/RunCMake/ExportSbom/SbomNamespaceAmbiguity.cmake create mode 100644 Tests/RunCMake/ExportSbom/SbomNamespaceFallback-install-check.cmake create mode 100644 Tests/RunCMake/ExportSbom/SbomNamespaceFallback.cmake create mode 100644 Tests/RunCMake/ExportSbom/TargetInMultipleSets-install-check.cmake create mode 100644 Tests/RunCMake/ExportSbom/TargetInMultipleSets.cmake create mode 100644 Tests/RunCMake/InstallSbom/DuplicateSbom-result.txt create mode 100644 Tests/RunCMake/InstallSbom/DuplicateSbom-stderr.txt create mode 100644 Tests/RunCMake/InstallSbom/DuplicateSbom.cmake create mode 100644 Tests/RunCMake/InstallSbom/EmptyNamespaceFallback-install-check.cmake create mode 100644 Tests/RunCMake/InstallSbom/EmptyNamespaceFallback-stderr.txt create mode 100644 Tests/RunCMake/InstallSbom/EmptyNamespaceFallback.cmake create mode 100644 Tests/RunCMake/InstallSbom/InstallExportPlusSbomSameSet.cmake create mode 100644 Tests/RunCMake/InstallSbom/MultiNamespaceAmbiguity-result.txt create mode 100644 Tests/RunCMake/InstallSbom/MultiNamespaceAmbiguity-stderr.txt create mode 100644 Tests/RunCMake/InstallSbom/MultiNamespaceAmbiguity.cmake create mode 100644 Tests/RunCMake/InstallSbom/MultiSetAmbiguity-result.txt create mode 100644 Tests/RunCMake/InstallSbom/MultiSetAmbiguity-stderr.txt create mode 100644 Tests/RunCMake/InstallSbom/MultiSetAmbiguity.cmake create mode 100644 Tests/RunCMake/InstallSbom/MultiSetSingleSbom-install-check.cmake create mode 100644 Tests/RunCMake/InstallSbom/MultiSetSingleSbom.cmake delete mode 100644 Tests/RunCMake/InstallSbom/ReferencesNonExportedTarget-install-check.cmake create mode 100644 Tests/RunCMake/InstallSbom/ReferencesNonExportedTarget-result.txt create mode 100644 Tests/RunCMake/InstallSbom/ReferencesNonExportedTarget-stderr.txt create mode 100644 Tests/RunCMake/InstallSbom/SbomNamespaceAmbiguity-install-check.cmake create mode 100644 Tests/RunCMake/InstallSbom/SbomNamespaceAmbiguity-stderr.txt create mode 100644 Tests/RunCMake/InstallSbom/SbomNamespaceAmbiguity.cmake create mode 100644 Tests/RunCMake/InstallSbom/SbomNamespaceFallback-install-check.cmake create mode 100644 Tests/RunCMake/InstallSbom/SbomNamespaceFallback.cmake create mode 100644 Tests/RunCMake/InstallSbom/TargetInMultipleSets-install-check.cmake create mode 100644 Tests/RunCMake/InstallSbom/TargetInMultipleSets.cmake create mode 100644 Tests/RunCMake/Sbom/DuplicateSbom.cmake create mode 100644 Tests/RunCMake/Sbom/EmptyNamespaceFallback-install-check.cmake create mode 100644 Tests/RunCMake/Sbom/EmptyNamespaceFallback.cmake create mode 100644 Tests/RunCMake/Sbom/InstallExportPlusSbomSameSet.cmake create mode 100644 Tests/RunCMake/Sbom/MultiNamespaceAmbiguity.cmake create mode 100644 Tests/RunCMake/Sbom/MultiSetAmbiguity.cmake create mode 100644 Tests/RunCMake/Sbom/MultiSetSingleSbom-install-check.cmake create mode 100644 Tests/RunCMake/Sbom/MultiSetSingleSbom.cmake delete mode 100644 Tests/RunCMake/Sbom/ReferencesNonExportedTarget-install-check.cmake create mode 100644 Tests/RunCMake/Sbom/SbomNamespaceAmbiguity-install-check.cmake create mode 100644 Tests/RunCMake/Sbom/SbomNamespaceAmbiguity.cmake create mode 100644 Tests/RunCMake/Sbom/SbomNamespaceFallback-install-check.cmake create mode 100644 Tests/RunCMake/Sbom/SbomNamespaceFallback.cmake create mode 100644 Tests/RunCMake/Sbom/TargetInMultipleSets-install-check.cmake create mode 100644 Tests/RunCMake/Sbom/TargetInMultipleSets.cmake diff --git a/Help/command/export.rst b/Help/command/export.rst index 7b7420599c..775282f553 100644 --- a/Help/command/export.rst +++ b/Help/command/export.rst @@ -174,7 +174,7 @@ Exporting Software Bill of Materials (SBOM) Documents .. code-block:: cmake - export(SBOM EXPORT + export(SBOM EXPORTS ... [FORMAT ] [PROJECT |NO_PROJECT_METADATA] [VERSION [.[.[.]]]] @@ -189,9 +189,10 @@ Exporting Software Bill of Materials (SBOM) Documents Experimental. Gated by ``CMAKE_EXPERIMENTAL_GENERATE_SBOM``. Generates a software bill of materials (SBOM) document describing the targets -in the export ```` and their dependencies in the build tree. -Files are written to the ``sbom/`` subdirectory of the current -build directory. +in the listed ```` export sets and their dependencies in the +build tree. Targets from all listed exports are aggregated into one document. +The generated SBOM will be written to the ``sbom/`` subdirectory of +the current build directory. See :command:`install(SBOM)` for details about the supported SBOM formats and a description of the other options. diff --git a/Help/command/install.rst b/Help/command/install.rst index e69d266749..75016b0cca 100644 --- a/Help/command/install.rst +++ b/Help/command/install.rst @@ -1213,7 +1213,7 @@ Signatures .. code-block:: cmake - install(SBOM EXPORT + install(SBOM EXPORTS ... [PROJECT |NO_PROJECT_METADATA] [DESTINATION ] [VERSION [.[.[.]]]] @@ -1232,9 +1232,11 @@ Signatures the interface is designed to allow additional SBOM formats or schema versions to be supported in future CMake releases. - Target installations are associated with the export ```` - using the ``EXPORT`` option of the :command:`install(TARGETS)` signature - documented above. If ``DESTINATION`` is not specified, a platform-specific + Target installations are associated with each export ```` + in the ``EXPORTS`` list using the ``EXPORTS`` option of the + :command:`install(TARGETS)` signature documented above. A single SBOM may + cover multiple export sets; targets from all listed exports are aggregated + into one document. If ``DESTINATION`` is not specified, a platform-specific default is used. Several options may be used to specify package metadata: diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index 231d0704ad..10cee356a5 100644 --- a/Source/CMakeLists.txt +++ b/Source/CMakeLists.txt @@ -214,8 +214,10 @@ add_library( cmExportBuildFileGenerator.cxx cmExportBuildPackageInfoGenerator.h cmExportBuildPackageInfoGenerator.cxx - cmExportBuildSbomGenerator.h - cmExportBuildSbomGenerator.cxx + cmBuildSbomGenerator.h + cmBuildSbomGenerator.cxx + cmBuildSbomBuilder.h + cmBuildSbomBuilder.cxx cmExportCMakeConfigGenerator.h cmExportCMakeConfigGenerator.cxx cmExportFileGenerator.h @@ -228,14 +230,12 @@ add_library( cmExportInstallFileGenerator.cxx cmExportInstallPackageInfoGenerator.h cmExportInstallPackageInfoGenerator.cxx - cmExportInstallSbomGenerator.h - cmExportInstallSbomGenerator.cxx cmExportPackageInfoGenerator.h cmExportPackageInfoGenerator.cxx cmExportTryCompileFileGenerator.h cmExportTryCompileFileGenerator.cxx - cmExportSbomGenerator.h - cmExportSbomGenerator.cxx + cmSbomBuilder.h + cmSbomBuilder.cxx cmExportSet.h cmExportSet.cxx cmExternalMakefileProjectGenerator.cxx @@ -369,8 +369,10 @@ add_library( cmInstallRuntimeDependencySet.cxx cmInstallRuntimeDependencySetGenerator.h cmInstallRuntimeDependencySetGenerator.cxx - cmInstallSbomExportGenerator.h - cmInstallSbomExportGenerator.cxx + cmInstallSbomGenerator.h + cmInstallSbomGenerator.cxx + cmInstallSbomBuilder.h + cmInstallSbomBuilder.cxx cmInstallScriptGenerator.h cmInstallScriptGenerator.cxx cmInstallSubdirectoryGenerator.h diff --git a/Source/cmBuildSbomBuilder.cxx b/Source/cmBuildSbomBuilder.cxx new file mode 100644 index 0000000000..fbec0e114c --- /dev/null +++ b/Source/cmBuildSbomBuilder.cxx @@ -0,0 +1,41 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file LICENSE.rst or https://cmake.org/licensing for details. */ +#include "cmBuildSbomBuilder.h" + +#include + +#include "cmGeneratorExpression.h" +#include "cmGeneratorTarget.h" +#include "cmGlobalGenerator.h" +#include "cmLocalGenerator.h" +#include "cmSbomArguments.h" + +cmBuildSbomBuilder::cmBuildSbomBuilder(cmSbomArguments args, + std::vector exportSets, + cmLocalGenerator* lg) + : cmSbomBuilder(std::move(args), std::move(exportSets), lg) +{ +} + +bool cmBuildSbomBuilder::Generate(std::ostream& os) +{ + if (!this->LocalGenerator) { + return false; + } + return this->GenerateForTargets(os, cmGeneratorExpression::BuildInterface); +} + +cmExportFileGenerator::ExportInfo cmBuildSbomBuilder::FindExportInfoFor( + cmGeneratorTarget const* target) const +{ + return target->GetLocalGenerator() + ->GetGlobalGenerator() + ->FindBuildExportInfo(target); +} + +cmSbomBuilder::SbomInfo cmBuildSbomBuilder::FindSbomInfoFor( + cmGeneratorTarget const* target) const +{ + return target->GetLocalGenerator()->GetGlobalGenerator()->FindBuildSbomInfo( + target); +} diff --git a/Source/cmBuildSbomBuilder.h b/Source/cmBuildSbomBuilder.h new file mode 100644 index 0000000000..6cca8468f8 --- /dev/null +++ b/Source/cmBuildSbomBuilder.h @@ -0,0 +1,32 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file LICENSE.rst or https://cmake.org/licensing for details. */ +#pragma once + +#include "cmConfigure.h" // IWYU pragma: keep + +#include +#include + +#include "cmExportFileGenerator.h" +#include "cmSbomBuilder.h" + +class cmExportSet; +class cmLocalGenerator; +class cmSbomArguments; + +/** Build-tree SBOM (`export(SBOM ...)`). Covers the targets in the + * associated export sets. */ +class cmBuildSbomBuilder final : public cmSbomBuilder +{ +public: + cmBuildSbomBuilder(cmSbomArguments args, + std::vector exportSets, + cmLocalGenerator* lg = nullptr); + + bool Generate(std::ostream& os) override; + +protected: + cmExportFileGenerator::ExportInfo FindExportInfoFor( + cmGeneratorTarget const* target) const override; + SbomInfo FindSbomInfoFor(cmGeneratorTarget const* target) const override; +}; diff --git a/Source/cmBuildSbomGenerator.cxx b/Source/cmBuildSbomGenerator.cxx new file mode 100644 index 0000000000..d77e583e5f --- /dev/null +++ b/Source/cmBuildSbomGenerator.cxx @@ -0,0 +1,16 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file LICENSE.rst or https://cmake.org/licensing for details. */ +#include "cmBuildSbomGenerator.h" + +#include "cmGeneratedFileStream.h" + +void cmBuildSbomGenerator::Compute(cmLocalGenerator* lg) +{ + this->Builder->Compute(lg); +} + +bool cmBuildSbomGenerator::GenerateForBuild() +{ + cmGeneratedFileStream os(this->OutputFile); + return this->Builder->Generate(os); +} diff --git a/Source/cmBuildSbomGenerator.h b/Source/cmBuildSbomGenerator.h new file mode 100644 index 0000000000..5c068353b4 --- /dev/null +++ b/Source/cmBuildSbomGenerator.h @@ -0,0 +1,59 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file LICENSE.rst or https://cmake.org/licensing for details. */ +#pragma once + +#include +#include +#include + +#include + +#include "cmBuildSbomBuilder.h" +#include "cmSbomArguments.h" + +class cmExportSet; +class cmGeneratorTarget; +class cmLocalGenerator; + +/** \class cmBuildSbomGenerator + * \brief Thin wrapper around cmBuildSbomBuilder for the build-tree SBOM case. + * + * Stored on cmMakefile at configure time. At generate time, + * ComputeBuildFileGenerators() calls Compute(lg) to resolve export set + * targets and supply the local generator — mirroring + * cmExportBuildFileGenerator. + */ +class cmBuildSbomGenerator +{ +public: + cmBuildSbomGenerator(cmSbomArguments args, + std::vector exportSets, + std::string outputFile) + : OutputFile(std::move(outputFile)) + , Builder(cm::make_unique(std::move(args), + std::move(exportSets))) + { + } + + void Compute(cmLocalGenerator* lg); + + std::string const& GetOutputFile() const { return this->OutputFile; } + + /** True if this SBOM directly describes `target`. Used by peer SBOMs to + * attribute cross-references when install(export) provenance is absent. */ + bool CoversTarget(cmGeneratorTarget const* target) const + { + return this->Builder->CoversTarget(target); + } + std::string const& GetPackageName() const + { + return this->Builder->GetPackageName(); + } + + /** Open the output file and write the SBOM document to it. */ + bool GenerateForBuild(); + +private: + std::string OutputFile; + std::unique_ptr Builder; +}; diff --git a/Source/cmExportBuildFileGenerator.cxx b/Source/cmExportBuildFileGenerator.cxx index 54b4ad5976..5feedfc8b3 100644 --- a/Source/cmExportBuildFileGenerator.cxx +++ b/Source/cmExportBuildFileGenerator.cxx @@ -3,7 +3,6 @@ #include "cmExportBuildFileGenerator.h" #include -#include #include #include #include @@ -177,33 +176,29 @@ void cmExportBuildFileGenerator::GetTargets( cmExportFileGenerator::ExportInfo cmExportBuildFileGenerator::FindExportInfo( cmGeneratorTarget const* target) const { - std::vector exportFiles; - std::set exportSets; - std::set namespaces; + return target->GetLocalGenerator() + ->GetGlobalGenerator() + ->FindBuildExportInfo(target); +} +cm::optional +cmExportBuildFileGenerator::FindRecordForTarget( + cmGeneratorTarget const* target) const +{ auto const& name = target->GetName(); - auto& allExportSets = - target->GetLocalGenerator()->GetGlobalGenerator()->GetBuildExportSets(); - - for (auto const& exp : allExportSets) { - cmExportBuildFileGenerator const* const bfg = exp.second; - cmExportSet const* const exportSet = bfg->GetExportSet(); - std::vector targets; - bfg->GetTargets(targets); - if (std::any_of( - targets.begin(), targets.end(), - [&name](TargetExport const& te) { return te.Name == name; })) { - if (exportSet) { - exportSets.insert(exportSet->GetName()); - } else { - exportSets.insert(exp.first); - } - exportFiles.push_back(exp.first); - namespaces.insert(bfg->GetNamespace()); - } + std::vector targets; + this->GetTargets(targets); + bool const contains = + std::any_of(targets.begin(), targets.end(), + [&name](TargetExport const& te) { return te.Name == name; }); + cm::optional result; + if (contains) { + ExportRecord rec; + rec.Name = this->ExportSet ? this->ExportSet->GetName() : std::string{}; + rec.Namespace = this->GetNamespace(); + result = rec; } - - return { exportFiles, exportSets, namespaces }; + return result; } void cmExportBuildFileGenerator::ComplainAboutMissingTarget( diff --git a/Source/cmExportBuildFileGenerator.h b/Source/cmExportBuildFileGenerator.h index 5e9e4c834a..0f6200d7dd 100644 --- a/Source/cmExportBuildFileGenerator.h +++ b/Source/cmExportBuildFileGenerator.h @@ -9,6 +9,7 @@ #include #include +#include #include #include "cmDiagnostics.h" @@ -56,6 +57,18 @@ public: } void SetExportSet(cmExportSet*); + struct ExportRecord + { + std::string Name; // export set name; empty for anonymous exports + std::string Namespace; // export namespace + }; + + /** If this export contains `target`, return a record identifying it + * (export set name + namespace). Used by cmGlobalGenerator to assemble + * a project-wide view of where targets are exported. */ + cm::optional FindRecordForTarget( + cmGeneratorTarget const* target) const; + /** Set the name of the C++ module directory. */ void SetCxxModuleDirectory(std::string cxx_module_dir) { diff --git a/Source/cmExportBuildSbomGenerator.cxx b/Source/cmExportBuildSbomGenerator.cxx deleted file mode 100644 index afb1491585..0000000000 --- a/Source/cmExportBuildSbomGenerator.cxx +++ /dev/null @@ -1,89 +0,0 @@ -/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying - file LICENSE.rst or https://cmake.org/licensing for details. */ -#include "cmExportBuildSbomGenerator.h" - -#include -#include -#include - -#include - -#include "cmGeneratorExpression.h" -#include "cmSbomArguments.h" -#include "cmSbomObject.h" -#include "cmSpdx.h" -#include "cmStringAlgorithms.h" - -class cmGeneratorTarget; - -cmExportBuildSbomGenerator::cmExportBuildSbomGenerator(cmSbomArguments args) - : cmExportSbomGenerator(args) -{ - this->SetNamespace(cmStrCat(this->GetPackageName(), "::"_s)); -} - -bool cmExportBuildSbomGenerator::GenerateMainFile(std::ostream& os) -{ - if (!this->CollectExports([&](cmGeneratorTarget const*) {})) { - return false; - } - - cmSbomDocument doc; - doc.Graph.reserve(256); - - cmSpdxCreationInfo const* ci = - insert_back(doc.Graph, this->GenerateCreationInfo()); - cmSpdxDocument* project = insert_back(doc.Graph, this->GenerateSbom(ci)); - std::vector targets; - - for (auto const& exp : this->Exports) { - cmGeneratorTarget const* target = exp.Target; - - ImportPropertyMap properties; - this->PopulateInterfaceProperties(target, properties); - this->PopulateInterfaceLinkLibrariesProperty( - target, cmGeneratorExpression::BuildInterface, properties); - this->PopulateLinkLibrariesProperty( - target, cmGeneratorExpression::BuildInterface, properties); - - targets.push_back( - TargetProperties{ insert_back(project->RootElements, - this->GenerateImportTarget(ci, target)), - target, std::move(properties) }); - } - - for (auto const& target : targets) { - this->GenerateProperties(doc, project, ci, target, targets); - } - - this->WriteSbom(doc, os); - return true; -} - -void cmExportBuildSbomGenerator::HandleMissingTarget( - std::string& /* link_libs */, cmGeneratorTarget const* /* depender */, - cmGeneratorTarget* /* dependee */) -{ -} - -std::string cmExportBuildSbomGenerator::GetCxxModulesDirectory() const -{ - return {}; -} - -cm::string_view cmExportBuildSbomGenerator::GetImportPrefixWithSlash() const -{ - return ""; -} - -std::string cmExportBuildSbomGenerator::GetCxxModuleFile( - std::string const& /*name*/) const -{ - return {}; -} - -void cmExportBuildSbomGenerator::GenerateCxxModuleConfigInformation( - std::string const& /*name*/, std::ostream& /*os*/) const -{ - // TODO -} diff --git a/Source/cmExportBuildSbomGenerator.h b/Source/cmExportBuildSbomGenerator.h deleted file mode 100644 index c84c37343b..0000000000 --- a/Source/cmExportBuildSbomGenerator.h +++ /dev/null @@ -1,41 +0,0 @@ -/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying - file LICENSE.rst or https://cmake.org/licensing for details. */ -#pragma once - -#include "cmConfigure.h" // IWYU pragma: keep - -#include -#include - -#include - -#include "cmExportBuildFileGenerator.h" -#include "cmExportSbomGenerator.h" - -class cmSbomArguments; - -class cmExportBuildSbomGenerator - : public cmExportBuildFileGenerator - , public cmExportSbomGenerator -{ -public: - cmExportBuildSbomGenerator(cmSbomArguments args); - -protected: - void HandleMissingTarget(std::string& link_libs, - cmGeneratorTarget const* depender, - cmGeneratorTarget* dependee) override; - bool GenerateMainFile(std::ostream& os) override; - void GenerateImportTargetsConfig(std::ostream&, std::string const&, - std::string const&) override - { - } - std::string GetCxxModulesDirectory() const override; - - cm::string_view GetImportPrefixWithSlash() const override; - - std::string GetCxxModuleFile(std::string const& /*name*/) const override; - - void GenerateCxxModuleConfigInformation(std::string const& /*name*/, - std::ostream& /*os*/) const override; -}; diff --git a/Source/cmExportCommand.cxx b/Source/cmExportCommand.cxx index d7a6ed2c49..1d76fed78e 100644 --- a/Source/cmExportCommand.cxx +++ b/Source/cmExportCommand.cxx @@ -15,6 +15,7 @@ #include "cmArgumentParser.h" #include "cmArgumentParserTypes.h" +#include "cmBuildSbomGenerator.h" #include "cmCryptoHash.h" #include "cmDiagnostics.h" #include "cmExecutionStatus.h" @@ -23,7 +24,6 @@ #include "cmExportBuildCMakeConfigGenerator.h" #include "cmExportBuildFileGenerator.h" #include "cmExportBuildPackageInfoGenerator.h" -#include "cmExportBuildSbomGenerator.h" #include "cmExportSet.h" #include "cmGeneratedFileStream.h" #include "cmGlobalGenerator.h" @@ -88,6 +88,29 @@ static void AddExportGenerator( makefile.AddExportBuildFileGenerator(std::move(exportGenerator)); } +static bool ValidateExportableTarget(std::string const& name, cmMakefile& mf, + cmGlobalGenerator* gg, + cmExecutionStatus& status) +{ + if (mf.IsAlias(name)) { + status.SetError(cmStrCat("given ALIAS target \"", name, + "\" which may not be exported.")); + return false; + } + cmTarget const* target = gg->FindTarget(name); + if (!target) { + status.SetError(cmStrCat("given target \"", name, + "\" which is not built by this project.")); + return false; + } + if (target->GetType() == cmStateEnums::UTILITY) { + status.SetError(cmStrCat("given custom target \"", name, + "\" which may not be exported.")); + return false; + } + return true; +} + static bool HandleTargetsMode(std::vector const& args, cmExecutionStatus& status) { @@ -163,25 +186,7 @@ static bool HandleTargetsMode(std::vector const& args, cmGlobalGenerator* gg = mf.GetGlobalGenerator(); for (std::string const& currentTarget : *arguments.Targets) { - if (mf.IsAlias(currentTarget)) { - std::ostringstream e; - e << "given ALIAS target \"" << currentTarget - << "\" which may not be exported."; - status.SetError(e.str()); - return false; - } - - if (cmTarget* target = gg->FindTarget(currentTarget)) { - if (target->GetType() == cmStateEnums::UTILITY) { - status.SetError("given custom target \"" + currentTarget + - "\" which may not be exported."); - return false; - } - } else { - std::ostringstream e; - e << "given target \"" << currentTarget - << "\" which is not built by this project."; - status.SetError(e.str()); + if (!ValidateExportableTarget(currentTarget, mf, gg, status)) { return false; } targets.emplace_back(currentTarget, std::string{}); @@ -413,10 +418,66 @@ static bool HandleSbomMode(std::vector const& args, return false; } - using arg_t = cmSbomArguments; - using gen_t = cmExportBuildSbomGenerator; - status.GetMakefile().SetExplicitlyGeneratesSbom(true); - return HandleSpecialExportMode(args, status); + struct SbomExportArguments + : public cmSbomArguments + , public ArgumentParser::ParseResult + { + ArgumentParser::NonEmpty> ExportSetNames; + + using cmSbomArguments::Check; + using ArgumentParser::ParseResult::Check; + }; + + auto parser = cmArgumentParser{}; + cmSbomArguments::Bind(parser); + parser.Bind("EXPORTS"_s, &SbomExportArguments::ExportSetNames); + + std::vector unknownArgs; + SbomExportArguments arguments = parser.Parse(args, &unknownArgs); + + if (!arguments.Check(args[0], &unknownArgs, status)) { + return false; + } + + if (arguments.ExportSetNames.empty()) { + status.SetError(cmStrCat(args[0], " missing EXPORTS.")); + return false; + } + + if (!arguments.Check(status) || !arguments.SetMetadataFromProject(status)) { + return false; + } + + cmMakefile& mf = status.GetMakefile(); + cmGlobalGenerator* gg = mf.GetGlobalGenerator(); + + std::string const dir = + arguments.GetDefaultDestination(mf.GetCurrentBinaryDirectory()); + std::string const fpath = cmStrCat(dir, '/', arguments.GetPackageFileName()); + + if (gg->IsBuildSbomFile(fpath)) { + status.SetError(cmStrCat("SBOM command already specified for the file "_s, + cmSystemTools::GetFilenameNameView(fpath), '.')); + return false; + } + + std::vector sets; + sets.reserve(arguments.ExportSetNames.size()); + for (std::string const& name : arguments.ExportSetNames) { + cm::optional const exportSet = + GetExportSet(name, gg, status); + if (!exportSet) { + return false; + } + sets.push_back(*exportSet); + } + + auto builder = cm::make_unique(arguments, sets, fpath); + + cmBuildSbomGenerator* rawPtr = builder.get(); + mf.AddBuildSbomGenerator(std::move(builder)); + gg->AddBuildSbomGenerator(rawPtr); + return true; } static bool HandleSetupMode(std::vector const& args, diff --git a/Source/cmExportFileGenerator.cxx b/Source/cmExportFileGenerator.cxx index 353aff7302..49704a4ce1 100644 --- a/Source/cmExportFileGenerator.cxx +++ b/Source/cmExportFileGenerator.cxx @@ -482,7 +482,7 @@ cm::optional cmResolveTargetsInGeneratorExpression( lastPos = nameStartPos + targetName.size() + 1; } - std::string errorString; + cm::optional errorString; pos = 0; lastPos = pos; while ((pos = input.find("$ cmResolveTargetsInGeneratorExpression( pos = 0; lastPos = pos; - while (errorString.empty() && + while (!errorString && (pos = input.find("$', nameStartPos); @@ -525,7 +525,7 @@ cm::optional cmResolveTargetsInGeneratorExpression( lastPos = nameStartPos + libName.size() + 1; } - while (errorString.empty() && + while (!errorString && (pos = input.find("$', nameStartPos); @@ -541,10 +541,7 @@ cm::optional cmResolveTargetsInGeneratorExpression( lastPos = nameStartPos + libName.size() + 1; } - if (!errorString.empty()) { - return errorString; - } - return cm::nullopt; + return errorString; } void cmExportFileGenerator::ResolveTargetsInGeneratorExpression( diff --git a/Source/cmExportFileGenerator.h b/Source/cmExportFileGenerator.h index 07ebe80b29..b3562c512a 100644 --- a/Source/cmExportFileGenerator.h +++ b/Source/cmExportFileGenerator.h @@ -34,6 +34,13 @@ public: cmExportFileGenerator(); virtual ~cmExportFileGenerator() = default; + struct ExportInfo + { + std::vector Files; + std::set Sets; + std::set Namespaces; + }; + /** Set the full path to the export file to generate. */ void SetExportFile(char const* mainFile); std::string const& GetMainExportFileName() const; @@ -145,13 +152,6 @@ protected: this->IssueMessage(MessageType::FATAL_ERROR, errorMessage); } - struct ExportInfo - { - std::vector Files; - std::set Sets; - std::set Namespaces; - }; - /** Find the set of export files and the unique namespace (if any) for a * target. */ virtual ExportInfo FindExportInfo(cmGeneratorTarget const* target) const = 0; diff --git a/Source/cmExportInstallFileGenerator.cxx b/Source/cmExportInstallFileGenerator.cxx index c63f8a93b1..a9f7437338 100644 --- a/Source/cmExportInstallFileGenerator.cxx +++ b/Source/cmExportInstallFileGenerator.cxx @@ -265,34 +265,9 @@ void cmExportInstallFileGenerator::HandleMissingTarget( cmExportFileGenerator::ExportInfo cmExportInstallFileGenerator::FindExportInfo( cmGeneratorTarget const* target) const { - std::vector exportFiles; - std::set exportSets; - std::set namespaces; - - auto const& name = target->GetName(); - auto& allExportSets = - target->GetLocalGenerator()->GetGlobalGenerator()->GetExportSets(); - - for (auto const& exp : allExportSets) { - auto const& exportSet = exp.second; - auto const& targets = exportSet.GetTargetExports(); - - if (std::any_of(targets.begin(), targets.end(), - [&name](std::unique_ptr const& te) { - return te->TargetName == name; - })) { - std::vector const* installs = - exportSet.GetInstallations(); - if (!installs->empty()) { - exportSets.insert(exp.first); - for (cmInstallExportGenerator const* install : *installs) { - exportFiles.push_back(install->GetDestinationFile()); - namespaces.insert(install->GetNamespace()); - } - } - } - } - return { exportFiles, exportSets, namespaces }; + return target->GetLocalGenerator() + ->GetGlobalGenerator() + ->FindInstallExportInfo(target); } void cmExportInstallFileGenerator::ComplainAboutMissingTarget( @@ -301,9 +276,9 @@ void cmExportInstallFileGenerator::ComplainAboutMissingTarget( { std::ostringstream e; e << "install(" << this->IEGen->InstallSubcommand() << " \"" - << this->GetExportName() << "\" ...) " - << "includes target \"" << depender->GetName() - << "\" which requires target \"" << dependee->GetName() << "\" "; + << this->GetExportName() << "\" ...) " << "includes target \"" + << depender->GetName() << "\" which requires target \"" + << dependee->GetName() << "\" "; if (exportInfo.Sets.empty()) { e << "that is not in any export set."; } else { @@ -330,9 +305,8 @@ void cmExportInstallFileGenerator::ComplainAboutDuplicateTarget( { std::ostringstream e; e << "install(" << this->IEGen->InstallSubcommand() << " \"" - << this->GetExportName() << "\" ...) " - << "includes target \"" << targetName - << "\" more than once in the export set."; + << this->GetExportName() << "\" ...) " << "includes target \"" + << targetName << "\" more than once in the export set."; this->ReportError(e.str()); } diff --git a/Source/cmExportInstallSbomGenerator.cxx b/Source/cmExportInstallSbomGenerator.cxx deleted file mode 100644 index f9641a1b48..0000000000 --- a/Source/cmExportInstallSbomGenerator.cxx +++ /dev/null @@ -1,247 +0,0 @@ -/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying - file LICENSE.rst or https://cmake.org/licensing for details. */ -#include "cmExportInstallSbomGenerator.h" - -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "cmExportSet.h" -#include "cmFileSetMetadata.h" -#include "cmGeneratorExpression.h" -#include "cmGeneratorFileSet.h" -#include "cmGeneratorTarget.h" -#include "cmInstallExportGenerator.h" -#include "cmInstallFileSetGenerator.h" -#include "cmLocalGenerator.h" -#include "cmMakefile.h" -#include "cmMessageType.h" -#include "cmOutputConverter.h" -#include "cmSbomArguments.h" -#include "cmSbomObject.h" -#include "cmSpdx.h" -#include "cmStateTypes.h" -#include "cmStringAlgorithms.h" -#include "cmSystemTools.h" -#include "cmTarget.h" -#include "cmTargetExport.h" - -cmExportInstallSbomGenerator::cmExportInstallSbomGenerator( - cmInstallExportGenerator* iegen, cmSbomArguments args) - : cmExportSbomGenerator(std::move(args)) - , cmExportInstallFileGenerator(iegen) -{ - this->SetNamespace(cmStrCat(this->GetPackageName(), "::"_s)); -} - -std::string cmExportInstallSbomGenerator::GetConfigImportFileGlob() const -{ - std::string glob = cmStrCat(this->FileBase, "@*", this->FileExt); - return glob; -} - -std::string const& cmExportInstallSbomGenerator::GetExportName() const -{ - return this->GetPackageName(); -} - -cm::string_view cmExportInstallSbomGenerator::GetImportPrefixWithSlash() const -{ - return "@prefix@/"_s; -} - -bool cmExportInstallSbomGenerator::GenerateMainFile(std::ostream& os) -{ - std::vector allTargets; - { - auto visitor = [&](cmTargetExport const* te) { allTargets.push_back(te); }; - - if (!this->CollectExports(visitor)) { - return false; - } - } - cmSbomDocument doc; - doc.Graph.reserve(256); - - cmSpdxCreationInfo const* ci = - insert_back(doc.Graph, this->GenerateCreationInfo()); - cmSpdxDocument* project = insert_back(doc.Graph, this->GenerateSbom(ci)); - std::vector targets; - targets.reserve(allTargets.size()); - - for (cmTargetExport const* te : allTargets) { - cmGeneratorTarget const* gt = te->Target; - ImportPropertyMap properties; - if (!this->PopulateInterfaceProperties(te, properties)) { - return false; - } - this->PopulateLinkLibrariesProperty( - gt, cmGeneratorExpression::InstallInterface, properties); - this->PopulateInterfaceLinkLibrariesProperty( - gt, cmGeneratorExpression::InstallInterface, properties); - - targets.push_back(TargetProperties{ - insert_back(project->RootElements, - this->GenerateImportTarget(ci, te->Target)), - te->Target, std::move(properties) }); - } - - for (auto const& target : targets) { - this->GenerateProperties(doc, project, ci, target, targets); - } - - this->WriteSbom(doc, os); - return true; -} - -void cmExportInstallSbomGenerator::GenerateImportTargetsConfig( - std::ostream& os, std::string const& config, std::string const& suffix) -{ - cmSbomDocument doc; - doc.Graph.reserve(256); - - cmSpdxCreationInfo const* ci = - insert_back(doc.Graph, this->GenerateCreationInfo()); - cmSpdxDocument* project = insert_back(doc.Graph, this->GenerateSbom(ci)); - - std::vector targets; - std::string cfg = (config.empty() ? "noconfig" : config); - - for (auto const& te : this->GetExportSet()->GetTargetExports()) { - ImportPropertyMap properties; - std::set importedLocations; - - if (this->GetExportTargetType(te.get()) != - cmStateEnums::INTERFACE_LIBRARY) { - this->PopulateImportProperties(config, suffix, te.get(), properties, - importedLocations); - } - this->PopulateInterfaceProperties(te.get(), properties); - this->PopulateInterfaceLinkLibrariesProperty( - te->Target, cmGeneratorExpression::InstallInterface, properties); - this->PopulateLinkLibrariesProperty( - te->Target, cmGeneratorExpression::InstallInterface, properties); - - targets.push_back(TargetProperties{ - insert_back(project->RootElements, - this->GenerateImportTarget(ci, te->Target)), - te->Target, std::move(properties) }); - } - - for (auto const& target : targets) { - this->GenerateProperties(doc, project, ci, target, targets); - } - - this->WriteSbom(doc, os); -} - -std::string cmExportInstallSbomGenerator::GenerateImportPrefix() const -{ - std::string expDest = this->IEGen->GetDestination(); - if (cmSystemTools::FileIsFullPath(expDest)) { - std::string const& installPrefix = - this->IEGen->GetLocalGenerator()->GetMakefile()->GetSafeDefinition( - "CMAKE_INSTALL_PREFIX"); - if (cmHasPrefix(expDest, installPrefix)) { - auto n = installPrefix.length(); - while (n < expDest.length() && expDest[n] == '/') { - ++n; - } - expDest = expDest.substr(n); - } else { - this->ReportError( - cmStrCat("install(SBOM \"", this->GetExportName(), - "\" ...) specifies DESTINATION \"", expDest, - "\" which is not a subdirectory of the install prefix.")); - return {}; - } - } - - if (expDest.empty()) { - return this->GetInstallPrefix(); - } - return cmStrCat(this->GetImportPrefixWithSlash(), expDest); -} - -void cmExportInstallSbomGenerator::HandleMissingTarget( - std::string& /* link_libs */, cmGeneratorTarget const* /* depender */, - cmGeneratorTarget* /* dependee */) -{ -} - -bool cmExportInstallSbomGenerator::CheckInterfaceDirs( - std::string const& /* prepro */, cmGeneratorTarget const* /* target */, - std::string const& /* prop */) const -{ - return true; -} - -std::string cmExportInstallSbomGenerator::InstallNameDir( - cmGeneratorTarget const* target, std::string const& config) -{ - std::string install_name_dir; - - cmMakefile* mf = target->Target->GetMakefile(); - if (mf->IsOn("CMAKE_PLATFORM_HAS_INSTALLNAME")) { - install_name_dir = - target->GetInstallNameDirForInstallTree(config, "@prefix@"); - } - - return install_name_dir; -} - -std::string cmExportInstallSbomGenerator::GetCxxModulesDirectory() const -{ - return {}; -} - -void cmExportInstallSbomGenerator::GenerateCxxModuleConfigInformation( - std::string const&, std::ostream&) const -{ -} - -std::string cmExportInstallSbomGenerator::GetCxxModuleFile( - std::string const& /* name */) const -{ - return {}; -} - -cm::optional cmExportInstallSbomGenerator::GetFileSetDirectory( - cmGeneratorTarget* gte, cmTargetExport const* te, - cmGeneratorFileSet const* fileSet, cm::optional const& config) -{ - cmInstallFileSetGenerator::DestinationContext result = - te->FileSetGenerators.at(fileSet->GetName()) - ->GetDestination(gte, config.value_or("")); - - if (config && !result.HadContextSensitiveCondition) { - return {}; - } - - std::string const& type = fileSet->GetType(); - if (config && (type == cm::FileSetMetadata::CXX_MODULES)) { - cmMakefile* mf = gte->LocalGenerator->GetMakefile(); - std::ostringstream e; - e << "The \"" << gte->GetName() << "\" target's interface file set \"" - << fileSet->GetName() << "\" of type \"" << type - << "\" contains context-sensitive base file entries which is not " - "supported."; - mf->IssueMessage(MessageType::FATAL_ERROR, e.str()); - return {}; - } - - cm::optional dest = cmOutputConverter::EscapeForCMake( - result.UnescapedDestination, cmOutputConverter::WrapQuotes::NoWrap); - - if (!cmSystemTools::FileIsFullPath(result.UnescapedDestination)) { - dest = cmStrCat("@prefix@/"_s, *dest); - } - - return dest; -} diff --git a/Source/cmExportInstallSbomGenerator.h b/Source/cmExportInstallSbomGenerator.h deleted file mode 100644 index a6d4c5c636..0000000000 --- a/Source/cmExportInstallSbomGenerator.h +++ /dev/null @@ -1,69 +0,0 @@ -/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying - file LICENSE.rst or https://cmake.org/licensing for details. */ -#pragma once - -#include "cmConfigure.h" // IWYU pragma: keep - -#include -#include - -#include -#include - -#include "cmExportInstallFileGenerator.h" -#include "cmExportSbomGenerator.h" - -class cmGeneratorFileSet; -class cmGeneratorTarget; -class cmInstallExportGenerator; -class cmSbomArguments; -class cmTargetExport; - -class cmExportInstallSbomGenerator - : public cmExportSbomGenerator - , public cmExportInstallFileGenerator -{ -public: - /** Construct with the export installer that will install the - files. */ - cmExportInstallSbomGenerator(cmInstallExportGenerator* iegen, - cmSbomArguments arguments); - - /** Compute the globbing expression used to load per-config import - files from the main file. */ - std::string GetConfigImportFileGlob() const override; - -protected: - std::string const& GetExportName() const override; - - cm::string_view GetImportPrefixWithSlash() const override; - std::string GetCxxModuleFile(std::string const& name) const override; - void GenerateCxxModuleConfigInformation(std::string const&, - std::ostream& os) const override; - - // Implement virtual methods from the superclass. - bool GenerateMainFile(std::ostream& os) override; - void GenerateImportTargetsConfig(std::ostream& os, std::string const& config, - std::string const& suffix) override; - - void HandleMissingTarget(std::string& /* link_libs */, - cmGeneratorTarget const* /* depender */, - cmGeneratorTarget* /* dependee */) override; - - bool CheckInterfaceDirs(std::string const& /* prepro */, - cmGeneratorTarget const* /* target */, - std::string const& /* prop */) const override; - - char GetConfigFileNameSeparator() const override { return '@'; } - - std::string GenerateImportPrefix() const; - std::string InstallNameDir(cmGeneratorTarget const* target, - std::string const& config) override; - - std::string GetCxxModulesDirectory() const override; - - cm::optional GetFileSetDirectory( - cmGeneratorTarget* gte, cmTargetExport const* te, - cmGeneratorFileSet const* fileSet, - cm::optional const& config = {}); -}; diff --git a/Source/cmExportSbomGenerator.h b/Source/cmExportSbomGenerator.h deleted file mode 100644 index 6fb67a1681..0000000000 --- a/Source/cmExportSbomGenerator.h +++ /dev/null @@ -1,86 +0,0 @@ -/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying - file LICENSE.rst or https://cmake.org/licensing for details. */ -#pragma once - -#include "cmConfigure.h" // IWYU pragma: keep - -#include -#include -#include -#include - -#include "cmExportFileGenerator.h" -#include "cmFindPackageStack.h" -#include "cmGeneratorExpression.h" -#include "cmSbomArguments.h" - -class cmGeneratorTarget; -struct cmSbomDocument; -struct cmSpdxDocument; -struct cmSpdxPackage; -struct cmSpdxCreationInfo; - -class cmExportSbomGenerator : virtual public cmExportFileGenerator -{ -public: - cmExportSbomGenerator(cmSbomArguments args); - using cmExportFileGenerator::GenerateImportFile; - -protected: - using ImportPropertyMap = std::map; - - struct TargetProperties - { - cmSpdxPackage const* Package; - cmGeneratorTarget const* Target; - ImportPropertyMap Properties; - }; - - void WriteSbom(cmSbomDocument& doc, std::ostream& os) const; - - cmSpdxCreationInfo GenerateCreationInfo() const; - cmSpdxDocument GenerateSbom(cmSpdxCreationInfo const* ci) const; - cmSpdxPackage GenerateImportTarget(cmSpdxCreationInfo const* ci, - cmGeneratorTarget const* target) const; - - std::string const& GetPackageName() const { return this->PackageName; } - - bool GenerateImportFile(std::ostream& os) override; - bool AddPackageInformation(cmSpdxPackage& artifact, std::string const& name, - cmPackageInformation const& package) const; - - bool GenerateProperties( - cmSbomDocument& doc, cmSpdxDocument* project, cmSpdxCreationInfo const* ci, - TargetProperties const& current, - std::vector const& allTargets) const; - - void GenerateLinkProperties( - cmSbomDocument& doc, cmSpdxDocument* project, cmSpdxCreationInfo const* ci, - std::string const& libraries, TargetProperties const& current, - std::vector const& allTargets) const; - - bool NoteLinkedTarget(cmGeneratorTarget const* target, - std::string const& linkedName, - cmGeneratorTarget const* linkedTarget) override; - - bool PopulateLinkLibrariesProperty(cmGeneratorTarget const* target, - cmGeneratorExpression::PreprocessContext, - ImportPropertyMap& properties); - -private: - struct LinkInfo - { - std::string Package; - std::string Component; - }; - - std::string const PackageName; - std::string const PackageVersion; - std::string const PackageDescription; - std::string const PackageWebsite; - std::string const PackageLicense; - std::string const PackageUrl; - cmSbomArguments::SbomFormat const PackageFormat; - std::map LinkTargets; - std::map Requirements; -}; diff --git a/Source/cmGlobalGenerator.cxx b/Source/cmGlobalGenerator.cxx index 3e1ef85438..55581e0e24 100644 --- a/Source/cmGlobalGenerator.cxx +++ b/Source/cmGlobalGenerator.cxx @@ -27,6 +27,7 @@ #include "cmAlgorithms.h" #include "cmArgumentParserTypes.h" #include "cmBuildArgs.h" +#include "cmBuildSbomGenerator.h" #include "cmCMakePath.h" #include "cmCPackPropertiesGenerator.h" #include "cmComputeTargetDepends.h" @@ -42,9 +43,10 @@ #include "cmGeneratedFileStream.h" #include "cmGeneratorExpression.h" #include "cmGeneratorTarget.h" +#include "cmInstallExportGenerator.h" #include "cmInstallGenerator.h" #include "cmInstallRuntimeDependencySet.h" -#include "cmInstallSbomExportGenerator.h" +#include "cmInstallSbomGenerator.h" #include "cmLinkLineComputer.h" #include "cmList.h" #include "cmListFileCache.h" @@ -63,6 +65,7 @@ #include "cmStringAlgorithms.h" #include "cmSyntheticTargetCache.h" #include "cmSystemTools.h" +#include "cmTargetExport.h" #include "cmValue.h" #include "cmVersion.h" #include "cmWorkingDirectory.h" @@ -312,6 +315,77 @@ void cmGlobalGenerator::AddBuildExportSet(cmExportBuildFileGenerator* gen) this->BuildExportSets[gen->GetMainExportFileName()] = gen; } +cmExportFileGenerator::ExportInfo cmGlobalGenerator::FindBuildExportInfo( + cmGeneratorTarget const* target) const +{ + cmExportFileGenerator::ExportInfo info; + for (auto const& exp : this->BuildExportSets) { + if (auto rec = exp.second->FindRecordForTarget(target)) { + info.Files.push_back(exp.first); + info.Sets.insert(rec->Name.empty() ? exp.first : rec->Name); + info.Namespaces.insert(rec->Namespace); + } + } + return info; +} + +cmExportFileGenerator::ExportInfo cmGlobalGenerator::FindInstallExportInfo( + cmGeneratorTarget const* target) const +{ + cmExportFileGenerator::ExportInfo info; + auto const& name = target->GetName(); + for (auto const& exp : this->ExportSets) { + auto const& exportSet = exp.second; + auto const& targets = exportSet.GetTargetExports(); + bool const contains = + std::any_of(targets.begin(), targets.end(), + [&name](std::unique_ptr const& te) { + return te->TargetName == name; + }); + if (!contains) { + continue; + } + auto const* installs = exportSet.GetInstallations(); + if (!installs || installs->empty()) { + continue; + } + info.Sets.insert(exp.first); + for (auto const* install : *installs) { + info.Files.push_back(install->GetDestinationFile()); + info.Namespaces.insert(install->GetNamespace()); + } + } + return info; +} + +#ifndef CMAKE_BOOTSTRAP +cmSbomBuilder::SbomInfo cmGlobalGenerator::FindBuildSbomInfo( + cmGeneratorTarget const* target) const +{ + cmSbomBuilder::SbomInfo info; + for (cmBuildSbomGenerator const* g : this->BuildSbomGenerators) { + if (g->CoversTarget(target)) { + info.Packages.push_back(g->GetPackageName()); + } + } + std::sort(info.Packages.begin(), info.Packages.end()); + return info; +} + +cmSbomBuilder::SbomInfo cmGlobalGenerator::FindInstallSbomInfo( + cmGeneratorTarget const* target) const +{ + cmSbomBuilder::SbomInfo info; + for (cmInstallSbomGenerator const* g : this->InstallSbomGenerators) { + if (g->CoversTarget(target)) { + info.Packages.push_back(g->GetPackageName()); + } + } + std::sort(info.Packages.begin(), info.Packages.end()); + return info; +} +#endif + void cmGlobalGenerator::AddBuildExportExportSet( cmExportBuildFileGenerator* gen) { @@ -319,6 +393,17 @@ void cmGlobalGenerator::AddBuildExportExportSet( this->AddBuildExportSet(gen); } +void cmGlobalGenerator::AddBuildSbomGenerator(cmBuildSbomGenerator* gen) +{ + this->BuildSbomGenerators.push_back(gen); +} + +void cmGlobalGenerator::AddInstallSbomGenerator( + cmInstallSbomGenerator const* gen) +{ + this->InstallSbomGenerators.push_back(gen); +} + void cmGlobalGenerator::ForceLinkerLanguages() { } @@ -413,6 +498,24 @@ bool cmGlobalGenerator::IsExportedTargetsFile( return !cm::contains(this->BuildExportExportSets, filename); } +bool cmGlobalGenerator::IsBuildSbomFile(std::string const& filepath) const +{ + return std::any_of(this->BuildSbomGenerators.begin(), + this->BuildSbomGenerators.end(), + [&filepath](cmBuildSbomGenerator const* g) { + return g->GetOutputFile() == filepath; + }); +} + +bool cmGlobalGenerator::IsInstallSbomFile(std::string const& filepath) const +{ + return std::any_of(this->InstallSbomGenerators.begin(), + this->InstallSbomGenerators.end(), + [&filepath](cmInstallSbomGenerator const* g) { + return g->GetInstallFile() == filepath; + }); +} + // Find the make program for the generator, required for try compiles bool cmGlobalGenerator::FindMakeProgram(cmMakefile* mf) { @@ -1440,11 +1543,15 @@ bool cmGlobalGenerator::CheckALLOW_DUPLICATE_CUSTOM_TARGETS() const void cmGlobalGenerator::ComputeBuildFileGenerators() { for (unsigned int i = 0; i < this->LocalGenerators.size(); ++i) { - std::vector> const& gens = - this->Makefiles[i]->GetExportBuildFileGenerators(); - for (std::unique_ptr const& g : gens) { - g->Compute(this->LocalGenerators[i].get()); + cmLocalGenerator* lg = this->LocalGenerators[i].get(); + for (auto const& g : this->Makefiles[i]->GetExportBuildFileGenerators()) { + g->Compute(lg); } +#ifndef CMAKE_BOOTSTRAP + for (auto const& g : this->Makefiles[i]->GetBuildSbomGenerators()) { + g->Compute(lg); + } +#endif } } @@ -1621,10 +1728,10 @@ bool cmGlobalGenerator::Compute() bool sbomEnabled = cmExperimental::HasSupportEnabled( *this->Makefiles[0], cmExperimental::Feature::GenerateSbom); - // Automatically generate SBOM files if enabled. + // Automatically generate one SBOM per export set not already tied to an + // explicit install(SBOM) call. cmValue sbomFormat = this->GetGlobalSetting("CMAKE_INSTALL_SBOM_FORMATS"); - if (sbomFormat.IsSet() && !this->Makefiles[0]->ExplicitlyGeneratesSbom() && - sbomEnabled && !isTryCompile) { + if (sbomFormat.IsSet() && sbomEnabled && !isTryCompile) { std::string location = this->Makefiles[0]->GetSafeDefinition("CMAKE_INSTALL_LIBDIR"); if (location.empty()) { @@ -1632,16 +1739,26 @@ bool cmGlobalGenerator::Compute() } std::string projectName = this->LocalGenerators[0]->GetProjectName(); - cmSbomArguments sbomDefaultArgs; - sbomDefaultArgs.ProjectName = projectName; for (auto& exportSet : this->ExportSets) { + bool isCovered = + std::any_of(this->InstallSbomGenerators.cbegin(), + this->InstallSbomGenerators.cend(), + [&exportSet](cmInstallSbomGenerator const* g) { + return g->CoversExportSet(&exportSet.second); + }); + if (isCovered) { + continue; + } + cmSbomArguments sbomDefaultArgs; + sbomDefaultArgs.ProjectName = projectName; sbomDefaultArgs.PackageName = exportSet.first; std::string dest = cmStrCat(location, "/sbom/", projectName); this->Makefiles[0]->AddInstallGenerator( - cm::make_unique( - &exportSet.second, dest, "", std::vector(), "", + cm::make_unique( + std::vector{ &exportSet.second }, dest, "", + std::vector(), "", cmInstallGenerator::SelectMessageLevel(this->Makefiles[0].get()), - false, std::move(sbomDefaultArgs), "", + false, std::move(sbomDefaultArgs), cmInstallGenerator::CaptureContext(this->Makefiles[0].get()))); } } @@ -1752,6 +1869,17 @@ void cmGlobalGenerator::Generate() return; } } +#ifndef CMAKE_BOOTSTRAP + for (auto& sbomGen : this->BuildSbomGenerators) { + if (!sbomGen->GenerateForBuild()) { + if (!cmSystemTools::GetErrorOccurredFlag()) { + this->GetCMakeInstance()->IssueMessage(MessageType::FATAL_ERROR, + "Could not write SBOM file."); + } + return; + } + } +#endif // Update rule hashes. this->CheckRuleHashes(); diff --git a/Source/cmGlobalGenerator.h b/Source/cmGlobalGenerator.h index 3c1169987b..e9e5fbb93b 100644 --- a/Source/cmGlobalGenerator.h +++ b/Source/cmGlobalGenerator.h @@ -24,8 +24,10 @@ #include "cmBuildOptions.h" #include "cmCustomCommandLines.h" #include "cmDuration.h" +#include "cmExportFileGenerator.h" #include "cmExportSet.h" #include "cmLocalGenerator.h" +#include "cmSbomBuilder.h" #include "cmStateSnapshot.h" #include "cmStateTypes.h" #include "cmStringAlgorithms.h" @@ -50,6 +52,8 @@ class cmBuildArgs; class cmDirectoryId; class cmExportBuildFileGenerator; class cmExternalMakefileProjectGenerator; +class cmBuildSbomGenerator; +class cmInstallSbomGenerator; class cmGeneratorTarget; class cmInstallRuntimeDependencySet; class cmLinkLineComputer; @@ -635,9 +639,54 @@ public: { return this->BuildExportSets; } + /** Scan all build-tree exports in the project and report which of them + * reference `target`. Used both by cmExportBuildFileGenerator (to resolve + * out-of-export link references) and by cmSbomBuilder (to record which + * export sets a target appears in for SBOM dependency tracking). */ + cmExportFileGenerator::ExportInfo FindBuildExportInfo( + cmGeneratorTarget const* target) const; + + /** Same as FindBuildExportInfo, but searches install-tree export sets + * (those registered via install(EXPORT ...)). */ + cmExportFileGenerator::ExportInfo FindInstallExportInfo( + cmGeneratorTarget const* target) const; + + /** Scan all build-tree SBOMs and report which of them cover `target`. */ + cmSbomBuilder::SbomInfo FindBuildSbomInfo( + cmGeneratorTarget const* target) const; + + /** Same as FindBuildSbomInfo, but searches install-tree SBOMs. */ + cmSbomBuilder::SbomInfo FindInstallSbomInfo( + cmGeneratorTarget const* target) const; void AddBuildExportSet(cmExportBuildFileGenerator* gen); void AddBuildExportExportSet(cmExportBuildFileGenerator* gen); + void AddBuildSbomGenerator(cmBuildSbomGenerator* gen); + std::vector const& GetBuildSbomGenerators() const + { + return this->BuildSbomGenerators; + } + + // Project-wide registry of install(SBOM) generators. + void AddInstallSbomGenerator(cmInstallSbomGenerator const* gen); + std::vector const& GetInstallSbomGenerators() + const + { + return this->InstallSbomGenerators; + } + bool IsExportedTargetsFile(std::string const& filename) const; + + /** True if any registered cmBuildSbomGenerator already targets this + * output file path. Used to diagnose duplicate `export(SBOM ...)` + * calls that would otherwise silently clobber each other's output. */ + bool IsBuildSbomFile(std::string const& filepath) const; + + /** True if any registered cmInstallSbomGenerator already targets this + * install file path (DESTINATION + filename). Used to diagnose + * duplicate `install(SBOM ...)` calls that would otherwise silently + * clobber each other at install time. */ + bool IsInstallSbomFile(std::string const& filepath) const; + cmExportBuildFileGenerator* GetExportedTargetsFile( std::string const& filename) const; void AddCMP0068WarnTarget(std::string const& target); @@ -824,6 +873,8 @@ protected: cmExportSetMap ExportSets; std::map BuildExportSets; std::map BuildExportExportSets; + std::vector BuildSbomGenerators; + std::vector InstallSbomGenerators; std::map AliasTargets; diff --git a/Source/cmInstallCommand.cxx b/Source/cmInstallCommand.cxx index b1dba43ddc..557016ac53 100644 --- a/Source/cmInstallCommand.cxx +++ b/Source/cmInstallCommand.cxx @@ -41,7 +41,7 @@ #include "cmInstallPackageInfoExportGenerator.h" #include "cmInstallRuntimeDependencySet.h" #include "cmInstallRuntimeDependencySetGenerator.h" -#include "cmInstallSbomExportGenerator.h" +#include "cmInstallSbomGenerator.h" #include "cmInstallScriptGenerator.h" #include "cmInstallTargetGenerator.h" #include "cmList.h" @@ -2513,16 +2513,13 @@ bool HandleSbomMode(std::vector const& args, cmInstallCommandArguments ica(helper.DefaultComponentName, *helper.Makefile); cmSbomArguments arguments; - ArgumentParser::NonEmpty exportName; - ArgumentParser::NonEmpty cxxModulesDirectory; + ArgumentParser::NonEmpty> exportNames; arguments.Bind(ica); - ica.Bind("EXPORT"_s, exportName); + ica.Bind("EXPORTS"_s, exportNames); // ica.Bind("CXX_MODULES_DIRECTORY"_s, cxxModulesDirectory); TODO? std::vector unknownArgs; - ica.Parse(args, &unknownArgs); - ArgumentParser::ParseResult result = ica.Parse(args, &unknownArgs); if (!result.Check(args[0], &unknownArgs, status)) { return false; @@ -2538,8 +2535,8 @@ bool HandleSbomMode(std::vector const& args, return false; } - if (exportName.empty()) { - status.SetError(cmStrCat(args[0], " missing EXPORT.")); + if (exportNames.empty()) { + status.SetError(cmStrCat(args[0], " missing EXPORTS.")); return false; } @@ -2558,25 +2555,39 @@ bool HandleSbomMode(std::vector const& args, } } - cmExportSet& exportSet = - helper.Makefile->GetGlobalGenerator()->GetExportSets()[exportName]; + cmGlobalGenerator* gg = helper.Makefile->GetGlobalGenerator(); + + std::string const fpath = + cmStrCat(dest, '/', arguments.GetPackageFileName()); + if (gg->IsInstallSbomFile(fpath)) { + status.SetError(cmStrCat("SBOM command already specified for the file "_s, + cmSystemTools::GetFilenameNameView(fpath), '.')); + return false; + } + + cmExportSetMap& allExportSets = gg->GetExportSets(); + std::vector sets; + sets.reserve(exportNames.size()); + for (std::string const& name : exportNames) { + sets.push_back(&allExportSets[name]); + } cmInstallGenerator::MessageLevel message = cmInstallGenerator::SelectMessageLevel(helper.Makefile); // Tell the global generator about any installation component names - // specified + // specified. helper.Makefile->GetGlobalGenerator()->AddInstallComponent( ica.GetComponent()); - helper.Makefile->SetExplicitlyGeneratesSbom(true); - // Create the export install generator. - helper.Makefile->AddInstallGenerator( - cm::make_unique( - &exportSet, dest, ica.GetPermissions(), ica.GetConfigurations(), - ica.GetComponent(), message, ica.GetExcludeFromAll(), - std::move(arguments), std::move(cxxModulesDirectory), - helper.CaptureContext())); + // Create the SBOM install generator. + auto sbomGen = cm::make_unique( + std::move(sets), dest, ica.GetPermissions(), ica.GetConfigurations(), + ica.GetComponent(), message, ica.GetExcludeFromAll(), std::move(arguments), + helper.CaptureContext()); + cmInstallSbomGenerator const* rawPtr = sbomGen.get(); + helper.Makefile->AddInstallGenerator(std::move(sbomGen)); + helper.Makefile->GetGlobalGenerator()->AddInstallSbomGenerator(rawPtr); return true; #else diff --git a/Source/cmInstallSbomBuilder.cxx b/Source/cmInstallSbomBuilder.cxx new file mode 100644 index 0000000000..5561ff5b06 --- /dev/null +++ b/Source/cmInstallSbomBuilder.cxx @@ -0,0 +1,42 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file LICENSE.rst or https://cmake.org/licensing for details. */ +#include "cmInstallSbomBuilder.h" + +#include + +#include "cmGeneratorExpression.h" +#include "cmGeneratorTarget.h" +#include "cmGlobalGenerator.h" +#include "cmLocalGenerator.h" +#include "cmSbomArguments.h" + +cmInstallSbomBuilder::cmInstallSbomBuilder( + cmSbomArguments args, std::vector exportSets, + cmLocalGenerator* lg) + : cmSbomBuilder(std::move(args), std::move(exportSets), lg) +{ +} + +bool cmInstallSbomBuilder::Generate(std::ostream& os) +{ + if (!this->LocalGenerator) { + return false; + } + return this->GenerateForTargets(os, cmGeneratorExpression::InstallInterface); +} + +cmExportFileGenerator::ExportInfo cmInstallSbomBuilder::FindExportInfoFor( + cmGeneratorTarget const* target) const +{ + return target->GetLocalGenerator() + ->GetGlobalGenerator() + ->FindInstallExportInfo(target); +} + +cmSbomBuilder::SbomInfo cmInstallSbomBuilder::FindSbomInfoFor( + cmGeneratorTarget const* target) const +{ + return target->GetLocalGenerator() + ->GetGlobalGenerator() + ->FindInstallSbomInfo(target); +} diff --git a/Source/cmInstallSbomBuilder.h b/Source/cmInstallSbomBuilder.h new file mode 100644 index 0000000000..cb436c7aa7 --- /dev/null +++ b/Source/cmInstallSbomBuilder.h @@ -0,0 +1,32 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file LICENSE.rst or https://cmake.org/licensing for details. */ +#pragma once + +#include "cmConfigure.h" // IWYU pragma: keep + +#include +#include + +#include "cmExportFileGenerator.h" +#include "cmSbomBuilder.h" + +class cmExportSet; +class cmLocalGenerator; +class cmSbomArguments; + +/** Install-tree SBOM (`install(SBOM ...)`). Covers the targets in the + * associated export sets. */ +class cmInstallSbomBuilder final : public cmSbomBuilder +{ +public: + cmInstallSbomBuilder(cmSbomArguments args, + std::vector exportSets, + cmLocalGenerator* lg = nullptr); + + bool Generate(std::ostream& os) override; + +protected: + cmExportFileGenerator::ExportInfo FindExportInfoFor( + cmGeneratorTarget const* target) const override; + SbomInfo FindSbomInfoFor(cmGeneratorTarget const* target) const override; +}; diff --git a/Source/cmInstallSbomExportGenerator.cxx b/Source/cmInstallSbomExportGenerator.cxx deleted file mode 100644 index 4072b44be5..0000000000 --- a/Source/cmInstallSbomExportGenerator.cxx +++ /dev/null @@ -1,30 +0,0 @@ -/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying -file LICENSE.rst or https://cmake.org/licensing for details. */ -#include "cmInstallSbomExportGenerator.h" - -#include - -#include - -#include "cmDiagnosticContext.h" -#include "cmExportInstallFileGenerator.h" -#include "cmExportInstallSbomGenerator.h" -#include "cmSbomArguments.h" - -class cmExportSet; - -cmInstallSbomExportGenerator::cmInstallSbomExportGenerator( - cmExportSet* exportSet, std::string destination, std::string filePermissions, - std::vector const& configurations, std::string component, - MessageLevel message, bool excludeFromAll, cmSbomArguments args, - std::string cxxModulesDirectory, cmDiagnosticContext context) - : cmInstallExportGenerator( - exportSet, std::move(destination), std::move(filePermissions), - configurations, std::move(component), message, excludeFromAll, - args.GetPackageFileName(), args.GetNamespace(), - std::move(cxxModulesDirectory), std::move(context)) -{ - this->EFGen = cm::make_unique(this, args); -} - -cmInstallSbomExportGenerator::~cmInstallSbomExportGenerator() = default; diff --git a/Source/cmInstallSbomExportGenerator.h b/Source/cmInstallSbomExportGenerator.h deleted file mode 100644 index eaddc55123..0000000000 --- a/Source/cmInstallSbomExportGenerator.h +++ /dev/null @@ -1,31 +0,0 @@ -/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying - file LICENSE.rst or https://cmake.org/licensing for details. */ -#pragma once - -#include -#include - -#include "cmInstallExportGenerator.h" - -class cmDiagnosticContext; -class cmExportSet; -class cmSbomArguments; - -class cmInstallSbomExportGenerator final : public cmInstallExportGenerator -{ -public: - cmInstallSbomExportGenerator(cmExportSet* exportSet, std::string destination, - std::string filePermissions, - std::vector const& configurations, - std::string component, MessageLevel message, - bool excludeFromAll, cmSbomArguments arguments, - std::string cxxModulesDirectory, - cmDiagnosticContext context); - cmInstallSbomExportGenerator(cmInstallSbomExportGenerator const&) = delete; - ~cmInstallSbomExportGenerator() override; - - cmInstallSbomExportGenerator& operator=( - cmInstallSbomExportGenerator const&) = delete; - - char const* InstallSubcommand() const override { return "SBOM"; } -}; diff --git a/Source/cmInstallSbomGenerator.cxx b/Source/cmInstallSbomGenerator.cxx new file mode 100644 index 0000000000..0224d0683a --- /dev/null +++ b/Source/cmInstallSbomGenerator.cxx @@ -0,0 +1,88 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file LICENSE.rst or https://cmake.org/licensing for details. */ +#include "cmInstallSbomGenerator.h" + +#include +#include + +#include + +#include "cmCryptoHash.h" +#include "cmDiagnosticContext.h" +#include "cmGeneratedFileStream.h" +#include "cmInstallSbomBuilder.h" +#include "cmInstallType.h" +#include "cmLocalGenerator.h" +#include "cmSbomArguments.h" +#include "cmStringAlgorithms.h" +#include "cmSystemTools.h" + +cmInstallSbomGenerator::cmInstallSbomGenerator( + std::vector exportSets, std::string destination, + std::string filePermissions, std::vector const& configurations, + std::string component, MessageLevel message, bool excludeFromAll, + cmSbomArguments args, cmDiagnosticContext context) + : cmInstallGenerator(std::move(destination), configurations, + std::move(component), message, excludeFromAll, false, + std::move(context)) + , FilePermissions(std::move(filePermissions)) + , SbomFileName(args.GetPackageFileName()) + , SbomFilePath(cmStrCat(this->Destination, '/', this->SbomFileName)) + , Builder(cm::make_unique(std::move(args), + std::move(exportSets))) +{ +} + +cmInstallSbomGenerator::~cmInstallSbomGenerator() = default; + +bool cmInstallSbomGenerator::Compute(cmLocalGenerator* lg) +{ + this->LocalGenerator = lg; + this->Builder->Compute(lg); + return true; +} + +bool cmInstallSbomGenerator::CoversTarget( + cmGeneratorTarget const* target) const +{ + return this->Builder->CoversTarget(target); +} + +std::string const& cmInstallSbomGenerator::GetPackageName() const +{ + return this->Builder->GetPackageName(); +} + +bool cmInstallSbomGenerator::CoversExportSet(cmExportSet const* set) const +{ + return this->Builder->CoversExportSet(set); +} + +void cmInstallSbomGenerator::GenerateScript(std::ostream& os) +{ + // Choose a temporary directory in the build tree to hold the generated SBOM. + cmCryptoHash hasher(cmCryptoHash::AlgoMD5); + std::string const tempDir = + cmStrCat(this->LocalGenerator->GetCurrentBinaryDirectory(), + "/CMakeFiles/Sbom/", hasher.HashString(this->Destination)); + + cmSystemTools::MakeDirectory(tempDir); + + this->TempSbomFilePath = cmStrCat(tempDir, '/', this->SbomFileName); + + // Generate the SBOM file now, at cmake generate time. + cmGeneratedFileStream sbomStream(this->TempSbomFilePath); + this->Builder->Generate(sbomStream); + + // Emit the cmake_install.cmake script to copy the file at install time. + this->cmInstallGenerator::GenerateScript(os); +} + +void cmInstallSbomGenerator::GenerateScriptActions(std::ostream& os, + Indent indent) +{ + std::vector files{ this->TempSbomFilePath }; + this->AddInstallRule(os, this->Destination, cmInstallType_FILES, files, + false, this->FilePermissions.c_str(), nullptr, nullptr, + nullptr, indent); +} diff --git a/Source/cmInstallSbomGenerator.h b/Source/cmInstallSbomGenerator.h new file mode 100644 index 0000000000..fd8dcdeb58 --- /dev/null +++ b/Source/cmInstallSbomGenerator.h @@ -0,0 +1,68 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file LICENSE.rst or https://cmake.org/licensing for details. */ +#pragma once + +#include "cmConfigure.h" // IWYU pragma: keep + +#include +#include +#include +#include + +#include "cmInstallGenerator.h" + +class cmDiagnosticContext; +class cmExportSet; +class cmGeneratorTarget; +class cmInstallSbomBuilder; +class cmLocalGenerator; +class cmSbomArguments; + +/** \class cmInstallSbomGenerator + * \brief Generate installation rules for SBOM files. + * + * Thin cmInstallGenerator subclass that owns a cmInstallSbomBuilder. + * At generate time it writes the SBOM into a temporary file in the build tree; + * the cmake_install.cmake script then copies that file to the install + * destination. + */ +class cmInstallSbomGenerator : public cmInstallGenerator +{ +public: + cmInstallSbomGenerator(std::vector exportSets, + std::string destination, std::string filePermissions, + std::vector const& configurations, + std::string component, MessageLevel message, + bool excludeFromAll, cmSbomArguments args, + cmDiagnosticContext context); + cmInstallSbomGenerator(cmInstallSbomGenerator const&) = delete; + ~cmInstallSbomGenerator() override; + + cmInstallSbomGenerator& operator=(cmInstallSbomGenerator const&) = delete; + + bool Compute(cmLocalGenerator* lg) override; + + std::string const& GetInstallFile() const { return this->SbomFilePath; } + + /** True if this SBOM directly describes `target`. Used by peer SBOMs to + * attribute cross-references when install(export) provenance is absent. */ + bool CoversTarget(cmGeneratorTarget const* target) const; + std::string const& GetPackageName() const; + + /** True if `set` is one of the export sets this SBOM was built from. + * Used by the autogen path to skip sets already explicitly tied to an + * install(SBOM). */ + bool CoversExportSet(cmExportSet const* set) const; + +protected: + void GenerateScript(std::ostream& os) override; + void GenerateScriptActions(std::ostream& os, Indent indent) override; + +private: + std::string TempSbomFilePath; + std::string const FilePermissions; + std::string const SbomFileName; + std::string const SbomFilePath; + cmLocalGenerator* LocalGenerator = nullptr; + std::unique_ptr Builder; +}; diff --git a/Source/cmMakefile.cxx b/Source/cmMakefile.cxx index d660a6df6b..3b4d04bb97 100644 --- a/Source/cmMakefile.cxx +++ b/Source/cmMakefile.cxx @@ -30,6 +30,9 @@ #include "cmsys/RegularExpression.hxx" #include "cmsys/String.h" +#ifndef CMAKE_BOOTSTRAP +# include "cmBuildSbomGenerator.h" +#endif #include "cmCustomCommand.h" #include "cmCustomCommandLines.h" #include "cmCustomCommandTypes.h" @@ -1009,6 +1012,20 @@ void cmMakefile::AddExportBuildFileGenerator( this->ExportBuildFileGenerators.emplace_back(std::move(gen)); } +#ifndef CMAKE_BOOTSTRAP +std::vector> const& +cmMakefile::GetBuildSbomGenerators() const +{ + return this->BuildSbomGenerators; +} + +void cmMakefile::AddBuildSbomGenerator( + std::unique_ptr gen) +{ + this->BuildSbomGenerators.emplace_back(std::move(gen)); +} +#endif + namespace { struct file_not_persistent { @@ -1473,16 +1490,6 @@ void cmMakefile::AddTestGenerator(std::unique_ptr g) } } -bool cmMakefile::ExplicitlyGeneratesSbom() const -{ - return this->ExplicitSbomGenerator; -} - -void cmMakefile::SetExplicitlyGeneratesSbom(bool status) -{ - this->ExplicitSbomGenerator = status; -} - void cmMakefile::PushFunctionScope(std::string const& fileName, cmPolicies::PolicyMap const& pm, cmDiagnostics::DiagnosticMap dm) diff --git a/Source/cmMakefile.h b/Source/cmMakefile.h index 955aa428b2..41b6f985a3 100644 --- a/Source/cmMakefile.h +++ b/Source/cmMakefile.h @@ -53,6 +53,7 @@ class cmCompiledGeneratorExpression; class cmCustomCommandLines; class cmExecutionStatus; class cmExpandedCommandArgument; +class cmBuildSbomGenerator; class cmExportBuildFileGenerator; class cmGeneratorExpressionEvaluationFile; class cmGlobalGenerator; @@ -901,9 +902,6 @@ public: //! Initialize a makefile from its parent void InitializeFromParent(cmMakefile* parent); - bool ExplicitlyGeneratesSbom() const; - void SetExplicitlyGeneratesSbom(bool status = true); - void AddInstallGenerator(std::unique_ptr g); std::vector>& GetInstallGenerators() @@ -1113,6 +1111,12 @@ public: void AddExportBuildFileGenerator( std::unique_ptr gen); +#ifndef CMAKE_BOOTSTRAP + std::vector> const& + GetBuildSbomGenerators() const; + void AddBuildSbomGenerator(std::unique_ptr gen); +#endif + // Maintain a stack of package roots to allow nested PACKAGE_ROOT_PATH // searches std::deque> FindPackageRootPathStack; @@ -1285,6 +1289,10 @@ private: std::vector> ExportBuildFileGenerators; +#ifndef CMAKE_BOOTSTRAP + std::vector> BuildSbomGenerators; +#endif + std::vector> EvaluationFiles; @@ -1343,7 +1351,6 @@ private: cmFindPackageStack FindPackageStack; unsigned int FindPackageStackNextIndex = 0; - bool ExplicitSbomGenerator = false; bool DebugFindPkg = false; bool CheckSystemVars; diff --git a/Source/cmExportSbomGenerator.cxx b/Source/cmSbomBuilder.cxx similarity index 55% rename from Source/cmExportSbomGenerator.cxx rename to Source/cmSbomBuilder.cxx index 6b29404a74..b4add759ef 100644 --- a/Source/cmExportSbomGenerator.cxx +++ b/Source/cmSbomBuilder.cxx @@ -1,25 +1,33 @@ /* 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 "cmSbomBuilder.h" +#include #include +#include #include #include #include +#include #include #include #include #include #include +#include #include "cmArgumentParserTypes.h" #include "cmDiagnostics.h" +#include "cmExportFileGenerator.h" +#include "cmExportSet.h" #include "cmFindPackageStack.h" #include "cmGeneratorExpression.h" #include "cmGeneratorTarget.h" #include "cmList.h" +#include "cmLocalGenerator.h" #include "cmMakefile.h" +#include "cmMessageType.h" #include "cmSbomArguments.h" #include "cmSbomObject.h" #include "cmSpdx.h" @@ -28,6 +36,7 @@ #include "cmStringAlgorithms.h" #include "cmSystemTools.h" #include "cmTarget.h" +#include "cmTargetExport.h" #include "cmValue.h" cmSpdxPackage::PurposeId GetPurpose(cmStateEnums::TargetType type) @@ -50,24 +59,94 @@ cmSpdxPackage::PurposeId GetPurpose(cmStateEnums::TargetType type) } } -cmExportSbomGenerator::cmExportSbomGenerator(cmSbomArguments args) - : PackageName(std::move(args.PackageName)) +cmSbomBuilder::cmSbomBuilder(cmSbomArguments args, + std::vector exportSets, + cmLocalGenerator* lg) + : LocalGenerator(lg) + , ExportSets(std::move(exportSets)) + , PackageName(std::move(args.PackageName)) + , Namespace(cmStrCat(this->PackageName, "::"_s)) , PackageVersion(std::move(args.Version)) , PackageDescription(std::move(args.Description)) , PackageWebsite(std::move(args.Website)) - , PackageLicense(std::move(args.License)) , PackageUrl(std::move(args.PackageUrl)) + , PackageLicense(std::move(args.License)) , PackageFormat(args.GetFormat()) { } -bool cmExportSbomGenerator::GenerateImportFile(std::ostream& os) +std::set cmSbomBuilder::CollectTargets() const { - return this->GenerateMainFile(os); + std::set targets; + for (cmExportSet* exportSet : this->ExportSets) { + for (auto const& te : exportSet->GetTargetExports()) { + if (cmGeneratorTarget const* gt = + this->LocalGenerator->FindGeneratorTargetToUse(te->TargetName)) { + targets.emplace(gt); + } + } + } + return targets; } -void cmExportSbomGenerator::WriteSbom(cmSbomDocument& doc, - std::ostream& os) const +void cmSbomBuilder::Compute(cmLocalGenerator* lg) +{ + this->LocalGenerator = lg; + if (!lg) { + return; + } + for (cmExportSet* es : this->ExportSets) { + es->Compute(lg); + } + // Populate the cache now (rather than at Generate time) so peer SBOMs can + // query CoversTarget() during their own NoteLinkedTarget walks. + this->SbomTargets = this->CollectTargets(); +} + +bool cmSbomBuilder::CoversTarget(cmGeneratorTarget const* target) const +{ + return cm::contains(this->SbomTargets, target); +} + +bool cmSbomBuilder::CoversExportSet(cmExportSet const* set) const +{ + return std::find(this->ExportSets.cbegin(), this->ExportSets.cend(), set) != + this->ExportSets.cend(); +} + +bool cmSbomBuilder::GenerateForTargets( + std::ostream& os, cmGeneratorExpression::PreprocessContext preprocessContext) +{ + cmSbomDocument doc; + doc.Graph.reserve(256); + + cmSpdxCreationInfo const* ci = + insert_back(doc.Graph, this->GenerateCreationInfo()); + cmSpdxDocument* project = insert_back(doc.Graph, this->GenerateSbom(ci)); + std::vector targetProps; + targetProps.reserve(this->SbomTargets.size()); + + for (cmGeneratorTarget const* target : this->SbomTargets) { + ImportPropertyMap properties; + this->PopulateLinkLibrariesProperty(target, preprocessContext, properties); + this->PopulateInterfaceLinkLibrariesProperty(target, preprocessContext, + properties); + + targetProps.push_back( + TargetProperties{ insert_back(project->RootElements, + this->GenerateImportTarget(ci, target)), + target, std::move(properties) }); + } + + for (TargetProperties const& target : targetProps) { + this->GenerateProperties(doc, project, ci, target, targetProps); + } + + this->WriteSbom(doc, os); + return true; +} + +void cmSbomBuilder::WriteSbom(cmSbomDocument& doc, std::ostream& os) const { switch (this->PackageFormat) { case cmSbomArguments::SbomFormat::SPDX_3_0_JSON: @@ -78,7 +157,7 @@ void cmExportSbomGenerator::WriteSbom(cmSbomDocument& doc, } } -bool cmExportSbomGenerator::AddPackageInformation( +bool cmSbomBuilder::AddPackageInformation( cmSpdxPackage& artifact, std::string const& name, cmPackageInformation const& package) const { @@ -115,7 +194,7 @@ bool cmExportSbomGenerator::AddPackageInformation( return true; } -cmSpdxCreationInfo cmExportSbomGenerator::GenerateCreationInfo() const +cmSpdxCreationInfo cmSbomBuilder::GenerateCreationInfo() const { cmSpdxCreationInfo ci; ci.SpdxId = "_:Build#CreationInfo"; @@ -126,8 +205,7 @@ cmSpdxCreationInfo cmExportSbomGenerator::GenerateCreationInfo() const return ci; } -cmSpdxDocument cmExportSbomGenerator::GenerateSbom( - cmSpdxCreationInfo const* ci) const +cmSpdxDocument cmSbomBuilder::GenerateSbom(cmSpdxCreationInfo const* ci) const { cmSpdxDocument proj; proj.Name = PackageName; @@ -146,7 +224,7 @@ cmSpdxDocument cmExportSbomGenerator::GenerateSbom( return proj; } -cmSpdxPackage cmExportSbomGenerator::GenerateImportTarget( +cmSpdxPackage cmSbomBuilder::GenerateImportTarget( cmSpdxCreationInfo const* ci, cmGeneratorTarget const* target) const { cmSpdxPackage package; @@ -170,7 +248,7 @@ cmSpdxPackage cmExportSbomGenerator::GenerateImportTarget( return package; } -void cmExportSbomGenerator::GenerateLinkProperties( +void cmSbomBuilder::GenerateLinkProperties( cmSbomDocument& doc, cmSpdxDocument* project, cmSpdxCreationInfo const* ci, std::string const& libraries, TargetProperties const& current, std::vector const& allTargets) const @@ -270,7 +348,7 @@ void cmExportSbomGenerator::GenerateLinkProperties( } } -bool cmExportSbomGenerator::GenerateProperties( +bool cmSbomBuilder::GenerateProperties( cmSbomDocument& doc, cmSpdxDocument* proj, cmSpdxCreationInfo const* ci, TargetProperties const& current, std::vector const& allTargets) const @@ -282,7 +360,7 @@ bool cmExportSbomGenerator::GenerateProperties( return true; } -bool cmExportSbomGenerator::PopulateLinkLibrariesProperty( +bool cmSbomBuilder::PopulateLinkLibrariesProperty( cmGeneratorTarget const* target, cmGeneratorExpression::PreprocessContext preprocessRule, ImportPropertyMap& properties) @@ -297,8 +375,7 @@ bool cmExportSbomGenerator::PopulateLinkLibrariesProperty( std::string prepro = cmGeneratorExpression::Preprocess(*input, preprocessRule); if (!prepro.empty()) { - this->ResolveTargetsInGeneratorExpressions(prepro, target, - ReplaceFreeTargets); + this->ResolveTargetsInGeneratorExpressions(prepro, target); properties[linkIfaceProp] = prepro; hadLINK_LIBRARIES = true; } @@ -307,11 +384,38 @@ bool cmExportSbomGenerator::PopulateLinkLibrariesProperty( return hadLINK_LIBRARIES; } -bool cmExportSbomGenerator::NoteLinkedTarget( - cmGeneratorTarget const* target, std::string const& linkedName, - cmGeneratorTarget const* linkedTarget) +bool cmSbomBuilder::AddTargetNamespace(std::string& input, + cmGeneratorTarget const* target, + cmLocalGenerator const* lg) { - if (cm::contains(this->ExportedTargets, linkedTarget)) { + cmGeneratorTarget::TargetOrString resolved = + target->ResolveTargetReference(input, lg); + + cmGeneratorTarget* tgt = resolved.Target; + if (!tgt) { + input = resolved.String; + return false; + } + + if (tgt->IsImported()) { + input = tgt->GetName(); + return this->NoteLinkedTarget(target, input, tgt); + } + + if (this->SbomTargets.find(tgt) != this->SbomTargets.end()) { + input = this->Namespace + tgt->GetExportName(); + } else { + input = tgt->GetName(); + } + + return this->NoteLinkedTarget(target, input, tgt); +} + +bool cmSbomBuilder::NoteLinkedTarget(cmGeneratorTarget const* target, + std::string const& linkedName, + cmGeneratorTarget const* linkedTarget) +{ + if (cm::contains(this->SbomTargets, linkedTarget)) { this->LinkTargets.emplace(linkedName, LinkInfo{ "", linkedTarget->GetExportName() }); return true; @@ -358,8 +462,9 @@ bool cmExportSbomGenerator::NoteLinkedTarget( return true; } - // Target belongs to another export from this build. - auto const& exportInfo = this->FindExportInfo(linkedTarget); + // Target belongs to another export from this build or install. + // The leaf class chooses which export map to consult. + auto const& exportInfo = this->FindExportInfoFor(linkedTarget); if (exportInfo.Namespaces.size() == 1 && exportInfo.Sets.size() == 1) { auto const& linkNamespace = *exportInfo.Namespaces.begin(); if (!cmHasSuffix(linkNamespace, "::")) { @@ -367,8 +472,10 @@ bool cmExportSbomGenerator::NoteLinkedTarget( cmDiagnostics::CMD_AUTHOR, cmStrCat("Target \"", target->GetName(), "\" references target \"", linkedName, - "\", which does not use the standard namespace separator. " - "This is not allowed.")); + "\", whose export does not use the standard namespace " + "separator. The dependency will be recorded by its bare " + "target name without provenance.")); + return false; } std::string pkgName{ linkNamespace.data(), linkNamespace.size() - 2 }; @@ -382,8 +489,121 @@ bool cmExportSbomGenerator::NoteLinkedTarget( return true; } - // Target belongs to multiple namespaces or multiple export sets. - // cmExportFileGenerator::HandleMissingTarget should have complained about - // this already. + if (exportInfo.Sets.empty()) { + // install(export) provenance is unavailable. Fall back to SBOM-level + // attribution: any peer SBOM (of the same build/install mode) that + // covers this target lends its package name. install(export) wins + // when both are available; only reached here when install(export) is + // absent. + auto const& sbomInfo = this->FindSbomInfoFor(linkedTarget); + if (sbomInfo.Packages.empty()) { + target->Makefile->IssueMessage( + MessageType::FATAL_ERROR, + cmStrCat("Target \"", target->GetName(), "\" references target \"", + linkedName, + "\" which has no install(EXPORT)/export(EXPORT) namespace " + "and is not covered by any SBOM. An SBOM cannot attribute " + "this dependency. Give \"", + linkedTarget->GetName(), + "\" an install(EXPORT)/export(EXPORT) with a NAMESPACE, or " + "include it in an install(SBOM)/export(SBOM).")); + return false; + } + std::string const& pkgName = sbomInfo.Packages.front(); + if (sbomInfo.Packages.size() > 1) { + target->Makefile->IssueDiagnostic( + cmDiagnostics::CMD_AUTHOR, + cmStrCat( + "Target \"", target->GetName(), "\" references target \"", + linkedName, + "\" which has no install(EXPORT)/export(EXPORT) namespace and is " + "covered by multiple SBOMs: ", + cmJoin(sbomInfo.Packages, ", "), ". Attributing to \"", pkgName, + "\" (first alphabetically).")); + } + std::string component = linkedTarget->GetExportName(); + this->LinkTargets.emplace(linkedName, LinkInfo{ pkgName, component }); + this->Requirements[pkgName].Components.emplace(std::move(component)); + return true; + } + + std::ostringstream e; + e << "Target \"" << target->GetName() << "\" references target \"" + << linkedName << "\" "; + if (exportInfo.Sets.size() == 1) { + e << "that is in an export set which is exported multiple times " + "with different namespaces: "; + } else { + e << "that is in multiple export sets: "; + } + e << cmJoin(exportInfo.Files, ", ") << ".\n" + << "An SBOM cannot attribute a dependency exported in more than one " + "export set or with more than one namespace. Consider " + "consolidating the exports of the \"" + << linkedTarget->GetName() << "\" target to a single export."; + target->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str()); return false; } + +void cmSbomBuilder::ResolveTargetsInGeneratorExpression( + std::string& input, cmGeneratorTarget const* target, + cmLocalGenerator const* lg) +{ + auto err = cmResolveTargetsInGeneratorExpression( + input, [this, target, lg](std::string& name) { + return this->AddTargetNamespace(name, target, lg); + }); + if (err) { + target->GetLocalGenerator()->IssueMessage(MessageType::FATAL_ERROR, *err); + } +} + +void cmSbomBuilder::ResolveTargetsInGeneratorExpressions( + std::string& input, cmGeneratorTarget const* target) +{ + cmLocalGenerator const* lg = target->GetLocalGenerator(); + std::vector parts; + cmGeneratorExpression::Split(input, parts); + + std::string sep; + input.clear(); + for (std::string& li : parts) { + if (target->IsLinkLookupScope(li, lg)) { + continue; + } + if (cmGeneratorExpression::Find(li) == std::string::npos) { + this->AddTargetNamespace(li, target, lg); + } else { + this->ResolveTargetsInGeneratorExpression(li, target, lg); + } + input += sep + li; + sep = ";"; + } +} + +bool cmSbomBuilder::PopulateInterfaceLinkLibrariesProperty( + cmGeneratorTarget const* target, + cmGeneratorExpression::PreprocessContext preprocessRule, + ImportPropertyMap& properties) +{ + if (!target->IsLinkable()) { + return false; + } + static std::array const linkIfaceProps = { + { "INTERFACE_LINK_LIBRARIES", "INTERFACE_LINK_LIBRARIES_DIRECT", + "INTERFACE_LINK_LIBRARIES_DIRECT_EXCLUDE" } + }; + bool hadINTERFACE_LINK_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); + properties[linkIfaceProp] = prepro; + hadINTERFACE_LINK_LIBRARIES = true; + } + } + } + return hadINTERFACE_LINK_LIBRARIES; +} diff --git a/Source/cmSbomBuilder.h b/Source/cmSbomBuilder.h new file mode 100644 index 0000000000..60672e6a0a --- /dev/null +++ b/Source/cmSbomBuilder.h @@ -0,0 +1,179 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file LICENSE.rst or https://cmake.org/licensing for details. */ +#pragma once + +#include "cmConfigure.h" // IWYU pragma: keep + +#include +#include +#include +#include +#include + +#include "cmExportFileGenerator.h" +#include "cmFindPackageStack.h" +#include "cmGeneratorExpression.h" +#include "cmSbomArguments.h" + +class cmExportSet; +class cmGeneratorTarget; +class cmLocalGenerator; +struct cmSbomDocument; +struct cmSpdxDocument; +struct cmSpdxPackage; +struct cmSpdxCreationInfo; + +/** \class cmSbomBuilder + * \brief Abstract base for SBOM document generators. + * + * Concrete leaves (cmBuildSbomBuilder, cmInstallSbomBuilder) supply two + * pure virtuals: + * + * - Generate(): selects the generator-expression preprocess context + * (BuildInterface vs InstallInterface) and delegates to the shared + * GenerateForTargets() body. + * - FindExportInfoFor(): consults the mode's export map (build-tree vs + * install-tree) to resolve cross-export references. + * + * All shared SPDX assembly, link-graph walking, and serialization lives + * here so that the leaves stay trivial. + */ +class cmSbomBuilder +{ +public: + virtual ~cmSbomBuilder() = default; + + /** Compute phase: wire the local generator, run Compute() on the owned + * export sets, and populate the SbomTargets cache so peer SBOMs can + * query CoversTarget() before any Generate() runs. */ + void Compute(cmLocalGenerator* lg); + + /** Produce the SBOM document on `os`. Implementations pick the + * generator-expression preprocess context (BuildInterface vs + * InstallInterface) and delegate to GenerateForTargets. */ + virtual bool Generate(std::ostream& os) = 0; + + /** Identifier this SBOM publishes itself as (the SPDX document name and + * the namespace under which other SBOMs refer to its contents). */ + std::string const& GetPackageName() const { return this->PackageName; } + + /** True if `target` is one of the targets this SBOM directly describes. */ + bool CoversTarget(cmGeneratorTarget const* target) const; + + /** True if `set` is one of the export sets this SBOM was built from. */ + bool CoversExportSet(cmExportSet const* set) const; + + /** Names of peer SBOMs (same build/install mode) that cover a target. + * Used by NoteLinkedTarget to attribute a cross-reference when no + * install(export) namespace is available. Sorted alphabetically. */ + struct SbomInfo + { + std::vector Packages; + }; + +protected: + cmSbomBuilder(cmSbomArguments args, std::vector exportSets, + cmLocalGenerator* lg); + + /** Mode-specific: where does `target` appear in the project's exports? + * Build leaves consult cmGlobalGenerator::FindBuildExportInfo; + * install leaves consult cmGlobalGenerator::FindInstallExportInfo. */ + virtual cmExportFileGenerator::ExportInfo FindExportInfoFor( + cmGeneratorTarget const* target) const = 0; + + /** Mode-specific: which peer SBOMs cover `target`? + * Build leaves consult cmGlobalGenerator::FindBuildSbomInfo; + * install leaves consult cmGlobalGenerator::FindInstallSbomInfo. */ + virtual SbomInfo FindSbomInfoFor(cmGeneratorTarget const* target) const = 0; + + /** The set of targets the SBOM directly describes — derived from the + * associated export sets. */ + std::set CollectTargets() const; + + /** Generate an sbom for the targets in this->SbomTargets. Each leaf class + * calls this internally */ + bool GenerateForTargets( + std::ostream& os, + cmGeneratorExpression::PreprocessContext preprocessContext); + + using ImportPropertyMap = std::map; + + struct TargetProperties + { + cmSpdxPackage const* Package; + cmGeneratorTarget const* Target; + ImportPropertyMap Properties; + }; + + void WriteSbom(cmSbomDocument& doc, std::ostream& os) const; + + cmSpdxCreationInfo GenerateCreationInfo() const; + cmSpdxDocument GenerateSbom(cmSpdxCreationInfo const* ci) const; + cmSpdxPackage GenerateImportTarget(cmSpdxCreationInfo const* ci, + cmGeneratorTarget const* target) const; + + bool AddPackageInformation(cmSpdxPackage& artifact, std::string const& name, + cmPackageInformation const& package) const; + + bool GenerateProperties( + cmSbomDocument& doc, cmSpdxDocument* project, cmSpdxCreationInfo const* ci, + TargetProperties const& current, + std::vector const& allTargets) const; + + void GenerateLinkProperties( + cmSbomDocument& doc, cmSpdxDocument* project, cmSpdxCreationInfo const* ci, + std::string const& libraries, TargetProperties const& current, + std::vector const& allTargets) const; + + bool NoteLinkedTarget(cmGeneratorTarget const* target, + std::string const& linkedName, + cmGeneratorTarget const* linkedTarget); + + bool AddTargetNamespace(std::string& input, cmGeneratorTarget const* target, + cmLocalGenerator const* lg); + + void ResolveTargetsInGeneratorExpression(std::string& input, + cmGeneratorTarget const* target, + cmLocalGenerator const* lg); + void ResolveTargetsInGeneratorExpressions(std::string& input, + cmGeneratorTarget const* target); + + bool PopulateInterfaceLinkLibrariesProperty( + cmGeneratorTarget const* target, + cmGeneratorExpression::PreprocessContext preprocessRule, + ImportPropertyMap& properties); + + bool PopulateLinkLibrariesProperty(cmGeneratorTarget const* target, + cmGeneratorExpression::PreprocessContext, + ImportPropertyMap& properties); + + // Set at construction or via setters + cmLocalGenerator* LocalGenerator = nullptr; + + // Inputs + std::vector ExportSets; + +private: + struct LinkInfo + { + std::string Package; + std::string Component; + }; + + // Metadata + std::string const PackageName; + std::string const Namespace; + std::string const PackageVersion; + std::string const PackageDescription; + std::string const PackageWebsite; + std::string const PackageUrl; + std::string const PackageLicense; + cmSbomArguments::SbomFormat const PackageFormat; + + // Derived from inputs at generate time + std::set SbomTargets; + + // Accumulated during generation + std::map LinkTargets; + std::map Requirements; +}; diff --git a/Tests/RunCMake/EnvSbom/PartialCoverage-install-check.cmake b/Tests/RunCMake/EnvSbom/PartialCoverage-install-check.cmake new file mode 100644 index 0000000000..f33fa32497 --- /dev/null +++ b/Tests/RunCMake/EnvSbom/PartialCoverage-install-check.cmake @@ -0,0 +1,24 @@ +set(failures "") + +foreach(f explicit_root_sbom explicit_subdir_sbom) + if(NOT EXISTS "${RunCMake_TEST_INSTALL_DIR}/${f}.spdx.json") + list(APPEND failures "expected explicit SBOM ${f}.spdx.json to exist") + endif() +endforeach() + +foreach(f implicit_root implicit_subdir) + if(NOT EXISTS "${RunCMake_TEST_INSTALL_DIR}/lib/sbom/test_project/${f}.spdx.json") + list(APPEND failures "expected autogen SBOM ${f}.spdx.json to exist") + endif() +endforeach() + +foreach(f explicit_root explicit_subdir) + if(EXISTS "${RunCMake_TEST_INSTALL_DIR}/lib/sbom/test_project/${f}.spdx.json") + list(APPEND failures "autogen wrongly produced ${f}.spdx.json (should be suppressed by explicit install(SBOM))") + endif() +endforeach() + +if(failures) + string(REPLACE ";" "\n " msg "${failures}") + set(RunCMake_TEST_FAILED "${msg}") +endif() diff --git a/Tests/RunCMake/EnvSbom/PartialCoverage.cmake b/Tests/RunCMake/EnvSbom/PartialCoverage.cmake new file mode 100644 index 0000000000..cc49d14078 --- /dev/null +++ b/Tests/RunCMake/EnvSbom/PartialCoverage.cmake @@ -0,0 +1,12 @@ +project(test_project) +include(${CMAKE_CURRENT_LIST_DIR}/../Sbom/Setup.cmake) + +add_library(libA INTERFACE) +add_library(libB INTERFACE) + +install(TARGETS libA EXPORT explicit_root DESTINATION .) +install(TARGETS libB EXPORT implicit_root DESTINATION .) + +install(SBOM explicit_root_sbom EXPORTS explicit_root DESTINATION .) + +add_subdirectory(PartialCoverage_subdir) diff --git a/Tests/RunCMake/EnvSbom/PartialCoverage_subdir/CMakeLists.txt b/Tests/RunCMake/EnvSbom/PartialCoverage_subdir/CMakeLists.txt new file mode 100644 index 0000000000..aa5cb6ed80 --- /dev/null +++ b/Tests/RunCMake/EnvSbom/PartialCoverage_subdir/CMakeLists.txt @@ -0,0 +1,7 @@ +add_library(libC INTERFACE) +add_library(libD INTERFACE) + +install(TARGETS libC EXPORT explicit_subdir DESTINATION .) +install(TARGETS libD EXPORT implicit_subdir DESTINATION .) + +install(SBOM explicit_subdir_sbom EXPORTS explicit_subdir DESTINATION .) diff --git a/Tests/RunCMake/EnvSbom/ReferencesNonExportedTarget-install-check.cmake b/Tests/RunCMake/EnvSbom/ReferencesNonExportedTarget-install-check.cmake deleted file mode 100644 index d00e3c0c52..0000000000 --- a/Tests/RunCMake/EnvSbom/ReferencesNonExportedTarget-install-check.cmake +++ /dev/null @@ -1,2 +0,0 @@ -file(READ "${RunCMake_TEST_INSTALL_DIR}/lib/sbom/test_project/dog.spdx.json" content) -include(${CMAKE_CURRENT_LIST_DIR}/../Sbom/ReferencesNonExportedTarget-install-check.cmake) diff --git a/Tests/RunCMake/EnvSbom/ReferencesNonExportedTarget-result.txt b/Tests/RunCMake/EnvSbom/ReferencesNonExportedTarget-result.txt new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/Tests/RunCMake/EnvSbom/ReferencesNonExportedTarget-result.txt @@ -0,0 +1 @@ +1 diff --git a/Tests/RunCMake/EnvSbom/ReferencesNonExportedTarget-stderr.txt b/Tests/RunCMake/EnvSbom/ReferencesNonExportedTarget-stderr.txt new file mode 100644 index 0000000000..3dfbdb65da --- /dev/null +++ b/Tests/RunCMake/EnvSbom/ReferencesNonExportedTarget-stderr.txt @@ -0,0 +1,6 @@ +CMake Error in CMakeLists\.txt: + Target "canine" references target "mammal" which has no + install\(EXPORT\)/export\(EXPORT\) namespace and is not covered by any SBOM\. + An SBOM cannot attribute this dependency\. Give "mammal" an + install\(EXPORT\)/export\(EXPORT\) with a NAMESPACE, or include it in an + install\(SBOM\)/export\(SBOM\)\. diff --git a/Tests/RunCMake/EnvSbom/RunCMakeTest.cmake b/Tests/RunCMake/EnvSbom/RunCMakeTest.cmake index 7ce55145cf..e860160ac7 100644 --- a/Tests/RunCMake/EnvSbom/RunCMakeTest.cmake +++ b/Tests/RunCMake/EnvSbom/RunCMakeTest.cmake @@ -7,6 +7,15 @@ set(common_test_options "-DCMAKE_INSTALL_LIBDIR=lib" ) +function(run_cmake_error test) + set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/${test}-build) + set(RunCMake_TEST_OPTIONS ${common_test_options}) + if(NOT RunCMake_GENERATOR_IS_MULTI_CONFIG) + list(APPEND RunCMake_TEST_OPTIONS -DCMAKE_BUILD_TYPE=DEBUG) + endif() + run_cmake(${test}) +endfunction() + function(run_cmake_install test) set(extra_options ${ARGN}) set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/${test}-build) @@ -19,7 +28,9 @@ function(run_cmake_install test) run_cmake(${test}) set(RunCMake_TEST_NO_CLEAN TRUE) + set(RunCMake_TEST_OUTPUT_MERGE 1) run_cmake_command(${test}-build ${CMAKE_COMMAND} --build . --config Debug) + unset(RunCMake_TEST_OUTPUT_MERGE) run_cmake_command(${test}-install ${CMAKE_COMMAND} --install . --config Debug) endfunction() @@ -28,5 +39,7 @@ run_cmake_install(InterfaceTarget) run_cmake_install(SharedTarget) run_cmake_install(MissingPackageNamespace) -run_cmake_install(ReferencesNonExportedTarget) run_cmake_install(ProjectMetadata) +run_cmake_install(PartialCoverage) + +run_cmake_error(ReferencesNonExportedTarget) diff --git a/Tests/RunCMake/ExportSbom/ApplicationTarget.cmake b/Tests/RunCMake/ExportSbom/ApplicationTarget.cmake index 39b0c29541..fafc73c02b 100644 --- a/Tests/RunCMake/ExportSbom/ApplicationTarget.cmake +++ b/Tests/RunCMake/ExportSbom/ApplicationTarget.cmake @@ -2,6 +2,6 @@ include(${CMAKE_CURRENT_LIST_DIR}/../Sbom/ApplicationTarget.cmake) export( SBOM application_targets - EXPORT application_targets + EXPORTS application_targets FORMAT "spdx-3.0+json" ) diff --git a/Tests/RunCMake/ExportSbom/DuplicateSbom-result.txt b/Tests/RunCMake/ExportSbom/DuplicateSbom-result.txt new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/Tests/RunCMake/ExportSbom/DuplicateSbom-result.txt @@ -0,0 +1 @@ +1 diff --git a/Tests/RunCMake/ExportSbom/DuplicateSbom-stderr.txt b/Tests/RunCMake/ExportSbom/DuplicateSbom-stderr.txt new file mode 100644 index 0000000000..c0d4b1266a --- /dev/null +++ b/Tests/RunCMake/ExportSbom/DuplicateSbom-stderr.txt @@ -0,0 +1,2 @@ +CMake Error at DuplicateSbom\.cmake:[0-9]+ \(export\): + export SBOM command already specified for the file mySbom\.spdx\.json\. diff --git a/Tests/RunCMake/ExportSbom/DuplicateSbom.cmake b/Tests/RunCMake/ExportSbom/DuplicateSbom.cmake new file mode 100644 index 0000000000..ab51460408 --- /dev/null +++ b/Tests/RunCMake/ExportSbom/DuplicateSbom.cmake @@ -0,0 +1,5 @@ +include(${CMAKE_CURRENT_LIST_DIR}/../Sbom/DuplicateSbom.cmake) + +export(EXPORT setA FILE setA.cmake) +export(SBOM mySbom EXPORTS setA) +export(SBOM mySbom EXPORTS setA) diff --git a/Tests/RunCMake/ExportSbom/EmptyNamespaceFallback-install-check.cmake b/Tests/RunCMake/ExportSbom/EmptyNamespaceFallback-install-check.cmake new file mode 100644 index 0000000000..ce8270e5b0 --- /dev/null +++ b/Tests/RunCMake/ExportSbom/EmptyNamespaceFallback-install-check.cmake @@ -0,0 +1,2 @@ +file(READ "${RunCMake_TEST_BINARY_DIR}/sbom/mySbom/mySbom.spdx.json" content) +include(${CMAKE_CURRENT_LIST_DIR}/../Sbom/EmptyNamespaceFallback-install-check.cmake) diff --git a/Tests/RunCMake/ExportSbom/EmptyNamespaceFallback-stderr.txt b/Tests/RunCMake/ExportSbom/EmptyNamespaceFallback-stderr.txt new file mode 100644 index 0000000000..c6d0d96b86 --- /dev/null +++ b/Tests/RunCMake/ExportSbom/EmptyNamespaceFallback-stderr.txt @@ -0,0 +1,4 @@ +CMake Warning \(author\) in CMakeLists\.txt: + Target "libb" references target "liba", whose export does not use the + standard namespace separator\. The dependency will be recorded by its bare + target name without provenance\. diff --git a/Tests/RunCMake/ExportSbom/EmptyNamespaceFallback.cmake b/Tests/RunCMake/ExportSbom/EmptyNamespaceFallback.cmake new file mode 100644 index 0000000000..99ade04483 --- /dev/null +++ b/Tests/RunCMake/ExportSbom/EmptyNamespaceFallback.cmake @@ -0,0 +1,5 @@ +include(${CMAKE_CURRENT_LIST_DIR}/../Sbom/EmptyNamespaceFallback.cmake) + +export(EXPORT setA FILE setA.cmake) +export(EXPORT setB NAMESPACE B:: FILE setB.cmake) +export(SBOM mySbom EXPORTS setB) diff --git a/Tests/RunCMake/ExportSbom/InstallExportPlusSbomSameSet.cmake b/Tests/RunCMake/ExportSbom/InstallExportPlusSbomSameSet.cmake new file mode 100644 index 0000000000..5bf947ff42 --- /dev/null +++ b/Tests/RunCMake/ExportSbom/InstallExportPlusSbomSameSet.cmake @@ -0,0 +1,5 @@ +include(${CMAKE_CURRENT_LIST_DIR}/../Sbom/InstallExportPlusSbomSameSet.cmake) + +export(EXPORT export_set_a NAMESPACE A:: FILE export_set_a.cmake) +export(SBOM mySbom EXPORTS export_set_a) +export(EXPORT export_set_b FILE export_set_b.cmake) diff --git a/Tests/RunCMake/ExportSbom/InterfaceTarget.cmake b/Tests/RunCMake/ExportSbom/InterfaceTarget.cmake index 5bf0678d92..470b6729fc 100644 --- a/Tests/RunCMake/ExportSbom/InterfaceTarget.cmake +++ b/Tests/RunCMake/ExportSbom/InterfaceTarget.cmake @@ -2,5 +2,5 @@ include(${CMAKE_CURRENT_LIST_DIR}/../Sbom/InterfaceTarget.cmake) export( SBOM interface_targets - EXPORT interface_targets + EXPORTS interface_targets ) diff --git a/Tests/RunCMake/ExportSbom/MissingPackageNamespace.cmake b/Tests/RunCMake/ExportSbom/MissingPackageNamespace.cmake index be35e84968..d478765b1d 100644 --- a/Tests/RunCMake/ExportSbom/MissingPackageNamespace.cmake +++ b/Tests/RunCMake/ExportSbom/MissingPackageNamespace.cmake @@ -2,6 +2,6 @@ include(${CMAKE_CURRENT_LIST_DIR}/../Sbom/MissingPackageNamespace.cmake) export( SBOM test_targets - EXPORT test_targets + EXPORTS test_targets VERSION 1.0.2 ) diff --git a/Tests/RunCMake/ExportSbom/MultiNamespaceAmbiguity-result.txt b/Tests/RunCMake/ExportSbom/MultiNamespaceAmbiguity-result.txt new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/Tests/RunCMake/ExportSbom/MultiNamespaceAmbiguity-result.txt @@ -0,0 +1 @@ +1 diff --git a/Tests/RunCMake/ExportSbom/MultiNamespaceAmbiguity-stderr.txt b/Tests/RunCMake/ExportSbom/MultiNamespaceAmbiguity-stderr.txt new file mode 100644 index 0000000000..9be0fed7f1 --- /dev/null +++ b/Tests/RunCMake/ExportSbom/MultiNamespaceAmbiguity-stderr.txt @@ -0,0 +1,7 @@ +CMake Error in CMakeLists\.txt: + Target "libb" references target "liba" that is in an export set which is + exported multiple times with different namespaces:[^,]+setA-A\.cmake,[^,]+setA-OldA\.cmake\. + + An SBOM cannot attribute a dependency exported in more than one export set + or with more than one namespace\. Consider consolidating the exports of the + "liba" target to a single export\. diff --git a/Tests/RunCMake/ExportSbom/MultiNamespaceAmbiguity.cmake b/Tests/RunCMake/ExportSbom/MultiNamespaceAmbiguity.cmake new file mode 100644 index 0000000000..071542414a --- /dev/null +++ b/Tests/RunCMake/ExportSbom/MultiNamespaceAmbiguity.cmake @@ -0,0 +1,5 @@ +include(${CMAKE_CURRENT_LIST_DIR}/../Sbom/MultiNamespaceAmbiguity.cmake) + +export(EXPORT setA NAMESPACE A:: FILE setA-A.cmake) +export(EXPORT setA NAMESPACE OldA:: FILE setA-OldA.cmake) +export(SBOM mySbom EXPORTS setB) diff --git a/Tests/RunCMake/ExportSbom/MultiSetAmbiguity-result.txt b/Tests/RunCMake/ExportSbom/MultiSetAmbiguity-result.txt new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/Tests/RunCMake/ExportSbom/MultiSetAmbiguity-result.txt @@ -0,0 +1 @@ +1 diff --git a/Tests/RunCMake/ExportSbom/MultiSetAmbiguity-stderr.txt b/Tests/RunCMake/ExportSbom/MultiSetAmbiguity-stderr.txt new file mode 100644 index 0000000000..69837c2c61 --- /dev/null +++ b/Tests/RunCMake/ExportSbom/MultiSetAmbiguity-stderr.txt @@ -0,0 +1,8 @@ +CMake Error in CMakeLists\.txt: + Target "libb" references target "liba" that is in multiple export sets: + .*/setA1\.cmake, + .*/setA2\.cmake\. + + An SBOM cannot attribute a dependency exported in more than one export set + or with more than one namespace\. Consider consolidating the exports of the + "liba" target to a single export\. diff --git a/Tests/RunCMake/ExportSbom/MultiSetAmbiguity.cmake b/Tests/RunCMake/ExportSbom/MultiSetAmbiguity.cmake new file mode 100644 index 0000000000..f247f3befb --- /dev/null +++ b/Tests/RunCMake/ExportSbom/MultiSetAmbiguity.cmake @@ -0,0 +1,5 @@ +include(${CMAKE_CURRENT_LIST_DIR}/../Sbom/MultiSetAmbiguity.cmake) + +export(EXPORT setA1 NAMESPACE A1:: FILE setA1.cmake) +export(EXPORT setA2 NAMESPACE A2:: FILE setA2.cmake) +export(SBOM mySbom EXPORTS setB) diff --git a/Tests/RunCMake/ExportSbom/MultiSetSingleSbom-install-check.cmake b/Tests/RunCMake/ExportSbom/MultiSetSingleSbom-install-check.cmake new file mode 100644 index 0000000000..791c4cb6b5 --- /dev/null +++ b/Tests/RunCMake/ExportSbom/MultiSetSingleSbom-install-check.cmake @@ -0,0 +1,2 @@ +file(READ "${RunCMake_TEST_BINARY_DIR}/sbom/mySbom/mySbom.spdx.json" content) +include(${CMAKE_CURRENT_LIST_DIR}/../Sbom/MultiSetSingleSbom-install-check.cmake) diff --git a/Tests/RunCMake/ExportSbom/MultiSetSingleSbom.cmake b/Tests/RunCMake/ExportSbom/MultiSetSingleSbom.cmake new file mode 100644 index 0000000000..65eccc0076 --- /dev/null +++ b/Tests/RunCMake/ExportSbom/MultiSetSingleSbom.cmake @@ -0,0 +1,5 @@ +include(${CMAKE_CURRENT_LIST_DIR}/../Sbom/MultiSetSingleSbom.cmake) + +export(EXPORT setA NAMESPACE A:: FILE setA.cmake) +export(EXPORT setB NAMESPACE B:: FILE setB.cmake) +export(SBOM mySbom EXPORTS setA setB) diff --git a/Tests/RunCMake/ExportSbom/ProjectMetadata.cmake b/Tests/RunCMake/ExportSbom/ProjectMetadata.cmake index 6aaa68bf66..02b5ce3db4 100644 --- a/Tests/RunCMake/ExportSbom/ProjectMetadata.cmake +++ b/Tests/RunCMake/ExportSbom/ProjectMetadata.cmake @@ -2,7 +2,7 @@ include(${CMAKE_CURRENT_LIST_DIR}/../Sbom/ProjectMetadata.cmake) export( SBOM test_targets - EXPORT test_targets + EXPORTS test_targets DESCRIPTION "An eloquent description" LICENSE "BSD-3" HOMEPAGE_URL "www.example.com" diff --git a/Tests/RunCMake/ExportSbom/ReferencesNonExportedTarget-install-check.cmake b/Tests/RunCMake/ExportSbom/ReferencesNonExportedTarget-install-check.cmake deleted file mode 100644 index 465fcf0771..0000000000 --- a/Tests/RunCMake/ExportSbom/ReferencesNonExportedTarget-install-check.cmake +++ /dev/null @@ -1,2 +0,0 @@ -file(READ "${RunCMake_TEST_BINARY_DIR}/sbom/dog/dog.spdx.json" content) -include(${CMAKE_CURRENT_LIST_DIR}/../Sbom/ReferencesNonExportedTarget-install-check.cmake) diff --git a/Tests/RunCMake/ExportSbom/ReferencesNonExportedTarget-result.txt b/Tests/RunCMake/ExportSbom/ReferencesNonExportedTarget-result.txt new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/Tests/RunCMake/ExportSbom/ReferencesNonExportedTarget-result.txt @@ -0,0 +1 @@ +1 diff --git a/Tests/RunCMake/ExportSbom/ReferencesNonExportedTarget-stderr.txt b/Tests/RunCMake/ExportSbom/ReferencesNonExportedTarget-stderr.txt new file mode 100644 index 0000000000..3dfbdb65da --- /dev/null +++ b/Tests/RunCMake/ExportSbom/ReferencesNonExportedTarget-stderr.txt @@ -0,0 +1,6 @@ +CMake Error in CMakeLists\.txt: + Target "canine" references target "mammal" which has no + install\(EXPORT\)/export\(EXPORT\) namespace and is not covered by any SBOM\. + An SBOM cannot attribute this dependency\. Give "mammal" an + install\(EXPORT\)/export\(EXPORT\) with a NAMESPACE, or include it in an + install\(SBOM\)/export\(SBOM\)\. diff --git a/Tests/RunCMake/ExportSbom/ReferencesNonExportedTarget.cmake b/Tests/RunCMake/ExportSbom/ReferencesNonExportedTarget.cmake index dba5762396..9c06ac846f 100644 --- a/Tests/RunCMake/ExportSbom/ReferencesNonExportedTarget.cmake +++ b/Tests/RunCMake/ExportSbom/ReferencesNonExportedTarget.cmake @@ -1,3 +1,3 @@ include(${CMAKE_CURRENT_LIST_DIR}/../Sbom/ReferencesNonExportedTarget.cmake) -export(SBOM dog EXPORT dog) +export(SBOM dog EXPORTS dog) diff --git a/Tests/RunCMake/ExportSbom/Requirements-install-check.cmake b/Tests/RunCMake/ExportSbom/Requirements-install-check.cmake index 5cae65a43d..e0de7e7e2e 100644 --- a/Tests/RunCMake/ExportSbom/Requirements-install-check.cmake +++ b/Tests/RunCMake/ExportSbom/Requirements-install-check.cmake @@ -1,3 +1,3 @@ -file(READ "${RunCMake_TEST_BINARY_DIR}/sbom/bar/bar.spdx.json" BAR_CONTENT) -file(READ "${RunCMake_TEST_BINARY_DIR}/sbom/foo/foo.spdx.json" FOO_CONTENT) +file(READ "${RunCMake_TEST_BINARY_DIR}/sbom/bar_sbom/bar_sbom.spdx.json" BAR_CONTENT) +file(READ "${RunCMake_TEST_BINARY_DIR}/sbom/foo_sbom/foo_sbom.spdx.json" FOO_CONTENT) include(${CMAKE_CURRENT_LIST_DIR}/../Sbom/Requirements-install-check.cmake) diff --git a/Tests/RunCMake/ExportSbom/Requirements.cmake b/Tests/RunCMake/ExportSbom/Requirements.cmake index 41b38a78c3..756e691fc3 100644 --- a/Tests/RunCMake/ExportSbom/Requirements.cmake +++ b/Tests/RunCMake/ExportSbom/Requirements.cmake @@ -1,4 +1,5 @@ include(${CMAKE_CURRENT_LIST_DIR}/../Sbom/Requirements.cmake) -export(SBOM foo EXPORT foo) -export(SBOM bar EXPORT bar) +export(EXPORT foo NAMESPACE foo:: FILE foo.cmake) +export(SBOM foo_sbom EXPORTS foo) +export(SBOM bar_sbom EXPORTS bar) diff --git a/Tests/RunCMake/ExportSbom/RunCMakeTest.cmake b/Tests/RunCMake/ExportSbom/RunCMakeTest.cmake index 4956d44c27..25d2998059 100644 --- a/Tests/RunCMake/ExportSbom/RunCMakeTest.cmake +++ b/Tests/RunCMake/ExportSbom/RunCMakeTest.cmake @@ -5,6 +5,15 @@ set(common_test_options "-DCMAKE_EXPERIMENTAL_GENERATE_SBOM:STRING=ca494ed3-b261-4205-a01f-603c95e4cae0" ) +function(run_cmake_error test) + set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/${test}-build) + set(RunCMake_TEST_OPTIONS ${common_test_options}) + if(NOT RunCMake_GENERATOR_IS_MULTI_CONFIG) + list(APPEND RunCMake_TEST_OPTIONS -DCMAKE_BUILD_TYPE=DEBUG) + endif() + run_cmake(${test}) +endfunction() + function(run_cmake_install test) set(extra_options ${ARGN}) set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/${test}-build) @@ -17,15 +26,27 @@ function(run_cmake_install test) run_cmake(${test}) set(RunCMake_TEST_NO_CLEAN TRUE) + set(RunCMake_TEST_OUTPUT_MERGE 1) run_cmake_command(${test}-build ${CMAKE_COMMAND} --build . --config Debug) + unset(RunCMake_TEST_OUTPUT_MERGE) run_cmake_command(${test}-install ${CMAKE_COMMAND} --install . --config Debug) endfunction() run_cmake_install(ApplicationTarget) +run_cmake_install(InstallExportPlusSbomSameSet) run_cmake_install(InterfaceTarget) run_cmake_install(SharedTarget) run_cmake_install(Requirements) +run_cmake_install(SbomNamespaceFallback) +run_cmake_install(MultiSetSingleSbom) +run_cmake_install(TargetInMultipleSets) +run_cmake_install(EmptyNamespaceFallback -Wauthor) +run_cmake_install(SbomNamespaceAmbiguity -Wauthor) run_cmake_install(MissingPackageNamespace) -run_cmake_install(ReferencesNonExportedTarget) run_cmake_install(ProjectMetadata) + +run_cmake_error(ReferencesNonExportedTarget) +run_cmake_error(MultiNamespaceAmbiguity) +run_cmake_error(MultiSetAmbiguity) +run_cmake_error(DuplicateSbom) diff --git a/Tests/RunCMake/ExportSbom/SbomNamespaceAmbiguity-install-check.cmake b/Tests/RunCMake/ExportSbom/SbomNamespaceAmbiguity-install-check.cmake new file mode 100644 index 0000000000..31aa1bc36d --- /dev/null +++ b/Tests/RunCMake/ExportSbom/SbomNamespaceAmbiguity-install-check.cmake @@ -0,0 +1,2 @@ +file(READ "${RunCMake_TEST_BINARY_DIR}/sbom/bar_sbom/bar_sbom.spdx.json" BAR_CONTENT) +include(${CMAKE_CURRENT_LIST_DIR}/../Sbom/SbomNamespaceAmbiguity-install-check.cmake) diff --git a/Tests/RunCMake/ExportSbom/SbomNamespaceAmbiguity-stderr.txt b/Tests/RunCMake/ExportSbom/SbomNamespaceAmbiguity-stderr.txt new file mode 100644 index 0000000000..ea4234ff52 --- /dev/null +++ b/Tests/RunCMake/ExportSbom/SbomNamespaceAmbiguity-stderr.txt @@ -0,0 +1,4 @@ +CMake Warning \(author\) in CMakeLists\.txt: + Target "libc" references target "libb" which has no + install\(EXPORT\)/export\(EXPORT\) namespace and is covered by multiple SBOMs: + foo_sbom, foo_sbom2\. Attributing to "foo_sbom" \(first alphabetically\)\. diff --git a/Tests/RunCMake/ExportSbom/SbomNamespaceAmbiguity.cmake b/Tests/RunCMake/ExportSbom/SbomNamespaceAmbiguity.cmake new file mode 100644 index 0000000000..a667aca5b9 --- /dev/null +++ b/Tests/RunCMake/ExportSbom/SbomNamespaceAmbiguity.cmake @@ -0,0 +1,5 @@ +include(${CMAKE_CURRENT_LIST_DIR}/../Sbom/SbomNamespaceAmbiguity.cmake) + +export(SBOM foo_sbom EXPORTS foo) +export(SBOM foo_sbom2 EXPORTS foo) +export(SBOM bar_sbom EXPORTS bar) diff --git a/Tests/RunCMake/ExportSbom/SbomNamespaceFallback-install-check.cmake b/Tests/RunCMake/ExportSbom/SbomNamespaceFallback-install-check.cmake new file mode 100644 index 0000000000..df0183ccfa --- /dev/null +++ b/Tests/RunCMake/ExportSbom/SbomNamespaceFallback-install-check.cmake @@ -0,0 +1,3 @@ +file(READ "${RunCMake_TEST_BINARY_DIR}/sbom/bar/bar.spdx.json" BAR_CONTENT) +file(READ "${RunCMake_TEST_BINARY_DIR}/sbom/foo/foo.spdx.json" FOO_CONTENT) +include(${CMAKE_CURRENT_LIST_DIR}/../Sbom/SbomNamespaceFallback-install-check.cmake) diff --git a/Tests/RunCMake/ExportSbom/SbomNamespaceFallback.cmake b/Tests/RunCMake/ExportSbom/SbomNamespaceFallback.cmake new file mode 100644 index 0000000000..5cdb2f9f0a --- /dev/null +++ b/Tests/RunCMake/ExportSbom/SbomNamespaceFallback.cmake @@ -0,0 +1,4 @@ +include(${CMAKE_CURRENT_LIST_DIR}/../Sbom/SbomNamespaceFallback.cmake) + +export(SBOM foo EXPORTS foo) +export(SBOM bar EXPORTS bar) diff --git a/Tests/RunCMake/ExportSbom/SharedTarget.cmake b/Tests/RunCMake/ExportSbom/SharedTarget.cmake index 02143cfc77..c11cff770f 100644 --- a/Tests/RunCMake/ExportSbom/SharedTarget.cmake +++ b/Tests/RunCMake/ExportSbom/SharedTarget.cmake @@ -2,5 +2,5 @@ include(${CMAKE_CURRENT_LIST_DIR}/../Sbom/SharedTarget.cmake) export( SBOM shared_targets - EXPORT shared_targets + EXPORTS shared_targets ) diff --git a/Tests/RunCMake/ExportSbom/TargetInMultipleSets-install-check.cmake b/Tests/RunCMake/ExportSbom/TargetInMultipleSets-install-check.cmake new file mode 100644 index 0000000000..6a0d77fcd1 --- /dev/null +++ b/Tests/RunCMake/ExportSbom/TargetInMultipleSets-install-check.cmake @@ -0,0 +1,2 @@ +file(READ "${RunCMake_TEST_BINARY_DIR}/sbom/mySbom/mySbom.spdx.json" content) +include(${CMAKE_CURRENT_LIST_DIR}/../Sbom/TargetInMultipleSets-install-check.cmake) diff --git a/Tests/RunCMake/ExportSbom/TargetInMultipleSets.cmake b/Tests/RunCMake/ExportSbom/TargetInMultipleSets.cmake new file mode 100644 index 0000000000..244519e452 --- /dev/null +++ b/Tests/RunCMake/ExportSbom/TargetInMultipleSets.cmake @@ -0,0 +1,3 @@ +include(${CMAKE_CURRENT_LIST_DIR}/../Sbom/TargetInMultipleSets.cmake) + +export(SBOM mySbom EXPORTS setA1 setA2) diff --git a/Tests/RunCMake/InstallSbom/ApplicationTarget.cmake b/Tests/RunCMake/InstallSbom/ApplicationTarget.cmake index e08b9270c4..60c73ac690 100644 --- a/Tests/RunCMake/InstallSbom/ApplicationTarget.cmake +++ b/Tests/RunCMake/InstallSbom/ApplicationTarget.cmake @@ -1,7 +1,7 @@ include(${CMAKE_CURRENT_LIST_DIR}/../Sbom/ApplicationTarget.cmake) install(SBOM application_targets - EXPORT application_targets + EXPORTS application_targets FORMAT "spdx-3.0+json" DESTINATION . ) diff --git a/Tests/RunCMake/InstallSbom/DuplicateSbom-result.txt b/Tests/RunCMake/InstallSbom/DuplicateSbom-result.txt new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/Tests/RunCMake/InstallSbom/DuplicateSbom-result.txt @@ -0,0 +1 @@ +1 diff --git a/Tests/RunCMake/InstallSbom/DuplicateSbom-stderr.txt b/Tests/RunCMake/InstallSbom/DuplicateSbom-stderr.txt new file mode 100644 index 0000000000..97a197b79c --- /dev/null +++ b/Tests/RunCMake/InstallSbom/DuplicateSbom-stderr.txt @@ -0,0 +1,2 @@ +CMake Error at DuplicateSbom\.cmake:[0-9]+ \(install\): + install SBOM command already specified for the file mySbom\.spdx\.json\. diff --git a/Tests/RunCMake/InstallSbom/DuplicateSbom.cmake b/Tests/RunCMake/InstallSbom/DuplicateSbom.cmake new file mode 100644 index 0000000000..2959dd59a0 --- /dev/null +++ b/Tests/RunCMake/InstallSbom/DuplicateSbom.cmake @@ -0,0 +1,4 @@ +include(${CMAKE_CURRENT_LIST_DIR}/../Sbom/DuplicateSbom.cmake) + +install(SBOM mySbom EXPORTS setA DESTINATION .) +install(SBOM mySbom EXPORTS setA DESTINATION .) diff --git a/Tests/RunCMake/InstallSbom/EmptyNamespaceFallback-install-check.cmake b/Tests/RunCMake/InstallSbom/EmptyNamespaceFallback-install-check.cmake new file mode 100644 index 0000000000..3f61b5b541 --- /dev/null +++ b/Tests/RunCMake/InstallSbom/EmptyNamespaceFallback-install-check.cmake @@ -0,0 +1,2 @@ +file(READ "${RunCMake_TEST_INSTALL_DIR}/mySbom.spdx.json" content) +include(${CMAKE_CURRENT_LIST_DIR}/../Sbom/EmptyNamespaceFallback-install-check.cmake) diff --git a/Tests/RunCMake/InstallSbom/EmptyNamespaceFallback-stderr.txt b/Tests/RunCMake/InstallSbom/EmptyNamespaceFallback-stderr.txt new file mode 100644 index 0000000000..c6d0d96b86 --- /dev/null +++ b/Tests/RunCMake/InstallSbom/EmptyNamespaceFallback-stderr.txt @@ -0,0 +1,4 @@ +CMake Warning \(author\) in CMakeLists\.txt: + Target "libb" references target "liba", whose export does not use the + standard namespace separator\. The dependency will be recorded by its bare + target name without provenance\. diff --git a/Tests/RunCMake/InstallSbom/EmptyNamespaceFallback.cmake b/Tests/RunCMake/InstallSbom/EmptyNamespaceFallback.cmake new file mode 100644 index 0000000000..d6d5d575ca --- /dev/null +++ b/Tests/RunCMake/InstallSbom/EmptyNamespaceFallback.cmake @@ -0,0 +1,5 @@ +include(${CMAKE_CURRENT_LIST_DIR}/../Sbom/EmptyNamespaceFallback.cmake) + +install(EXPORT setA DESTINATION lib/cmake/A) +install(EXPORT setB NAMESPACE B:: DESTINATION lib/cmake/B) +install(SBOM mySbom EXPORTS setB DESTINATION .) diff --git a/Tests/RunCMake/InstallSbom/IgnoresInterfaceDirs.cmake b/Tests/RunCMake/InstallSbom/IgnoresInterfaceDirs.cmake index eed29084a7..1f5a640efc 100644 --- a/Tests/RunCMake/InstallSbom/IgnoresInterfaceDirs.cmake +++ b/Tests/RunCMake/InstallSbom/IgnoresInterfaceDirs.cmake @@ -14,6 +14,6 @@ install( ) install(SBOM interface_targets - EXPORT interface_targets + EXPORTS interface_targets DESTINATION . ) diff --git a/Tests/RunCMake/InstallSbom/InstallExportPlusSbomSameSet.cmake b/Tests/RunCMake/InstallSbom/InstallExportPlusSbomSameSet.cmake new file mode 100644 index 0000000000..d07a369660 --- /dev/null +++ b/Tests/RunCMake/InstallSbom/InstallExportPlusSbomSameSet.cmake @@ -0,0 +1,5 @@ +include(${CMAKE_CURRENT_LIST_DIR}/../Sbom/InstallExportPlusSbomSameSet.cmake) + +install(EXPORT export_set_a NAMESPACE A:: DESTINATION lib/cmake/setA) +install(SBOM mySbom EXPORTS export_set_a DESTINATION .) +install(EXPORT export_set_b DESTINATION lib/cmake/setB) diff --git a/Tests/RunCMake/InstallSbom/InterfaceTarget.cmake b/Tests/RunCMake/InstallSbom/InterfaceTarget.cmake index 9d88f16bdd..f31ba7799b 100644 --- a/Tests/RunCMake/InstallSbom/InterfaceTarget.cmake +++ b/Tests/RunCMake/InstallSbom/InterfaceTarget.cmake @@ -1,6 +1,6 @@ include(${CMAKE_CURRENT_LIST_DIR}/../Sbom/InterfaceTarget.cmake) install(SBOM interface_targets - EXPORT interface_targets + EXPORTS interface_targets DESTINATION . ) diff --git a/Tests/RunCMake/InstallSbom/MissingPackageNamespace.cmake b/Tests/RunCMake/InstallSbom/MissingPackageNamespace.cmake index 79c181229c..2e3cf4ec0b 100644 --- a/Tests/RunCMake/InstallSbom/MissingPackageNamespace.cmake +++ b/Tests/RunCMake/InstallSbom/MissingPackageNamespace.cmake @@ -2,6 +2,6 @@ include(${CMAKE_CURRENT_LIST_DIR}/../Sbom/MissingPackageNamespace.cmake) install(SBOM test_targets VERSION "1.0.2" - EXPORT test_targets + EXPORTS test_targets DESTINATION . ) diff --git a/Tests/RunCMake/InstallSbom/MultiNamespaceAmbiguity-result.txt b/Tests/RunCMake/InstallSbom/MultiNamespaceAmbiguity-result.txt new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/Tests/RunCMake/InstallSbom/MultiNamespaceAmbiguity-result.txt @@ -0,0 +1 @@ +1 diff --git a/Tests/RunCMake/InstallSbom/MultiNamespaceAmbiguity-stderr.txt b/Tests/RunCMake/InstallSbom/MultiNamespaceAmbiguity-stderr.txt new file mode 100644 index 0000000000..739ec831d1 --- /dev/null +++ b/Tests/RunCMake/InstallSbom/MultiNamespaceAmbiguity-stderr.txt @@ -0,0 +1,8 @@ +CMake Error in CMakeLists\.txt: + Target "libb" references target "liba" that is in an export set which is + exported multiple times with different namespaces: lib/cmake/A/setA\.cmake, + lib/cmake/OldA/setA\.cmake\. + + An SBOM cannot attribute a dependency exported in more than one export set + or with more than one namespace\. Consider consolidating the exports of the + "liba" target to a single export\. diff --git a/Tests/RunCMake/InstallSbom/MultiNamespaceAmbiguity.cmake b/Tests/RunCMake/InstallSbom/MultiNamespaceAmbiguity.cmake new file mode 100644 index 0000000000..b634a663c3 --- /dev/null +++ b/Tests/RunCMake/InstallSbom/MultiNamespaceAmbiguity.cmake @@ -0,0 +1,5 @@ +include(${CMAKE_CURRENT_LIST_DIR}/../Sbom/MultiNamespaceAmbiguity.cmake) + +install(EXPORT setA NAMESPACE A:: DESTINATION lib/cmake/A) +install(EXPORT setA NAMESPACE OldA:: DESTINATION lib/cmake/OldA) +install(SBOM mySbom EXPORTS setB DESTINATION .) diff --git a/Tests/RunCMake/InstallSbom/MultiSetAmbiguity-result.txt b/Tests/RunCMake/InstallSbom/MultiSetAmbiguity-result.txt new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/Tests/RunCMake/InstallSbom/MultiSetAmbiguity-result.txt @@ -0,0 +1 @@ +1 diff --git a/Tests/RunCMake/InstallSbom/MultiSetAmbiguity-stderr.txt b/Tests/RunCMake/InstallSbom/MultiSetAmbiguity-stderr.txt new file mode 100644 index 0000000000..45acf127cf --- /dev/null +++ b/Tests/RunCMake/InstallSbom/MultiSetAmbiguity-stderr.txt @@ -0,0 +1,7 @@ +CMake Error in CMakeLists\.txt: + Target "libb" references target "liba" that is in multiple export sets: + lib/cmake/A1/setA1\.cmake, lib/cmake/A2/setA2\.cmake\. + + An SBOM cannot attribute a dependency exported in more than one export set + or with more than one namespace\. Consider consolidating the exports of the + "liba" target to a single export\. diff --git a/Tests/RunCMake/InstallSbom/MultiSetAmbiguity.cmake b/Tests/RunCMake/InstallSbom/MultiSetAmbiguity.cmake new file mode 100644 index 0000000000..a434374919 --- /dev/null +++ b/Tests/RunCMake/InstallSbom/MultiSetAmbiguity.cmake @@ -0,0 +1,5 @@ +include(${CMAKE_CURRENT_LIST_DIR}/../Sbom/MultiSetAmbiguity.cmake) + +install(EXPORT setA1 NAMESPACE A1:: DESTINATION lib/cmake/A1) +install(EXPORT setA2 NAMESPACE A2:: DESTINATION lib/cmake/A2) +install(SBOM mySbom EXPORTS setB DESTINATION .) diff --git a/Tests/RunCMake/InstallSbom/MultiSetSingleSbom-install-check.cmake b/Tests/RunCMake/InstallSbom/MultiSetSingleSbom-install-check.cmake new file mode 100644 index 0000000000..e1e8aa15f2 --- /dev/null +++ b/Tests/RunCMake/InstallSbom/MultiSetSingleSbom-install-check.cmake @@ -0,0 +1,2 @@ +file(READ "${RunCMake_TEST_INSTALL_DIR}/mySbom.spdx.json" content) +include(${CMAKE_CURRENT_LIST_DIR}/../Sbom/MultiSetSingleSbom-install-check.cmake) diff --git a/Tests/RunCMake/InstallSbom/MultiSetSingleSbom.cmake b/Tests/RunCMake/InstallSbom/MultiSetSingleSbom.cmake new file mode 100644 index 0000000000..ac55d2f1ad --- /dev/null +++ b/Tests/RunCMake/InstallSbom/MultiSetSingleSbom.cmake @@ -0,0 +1,5 @@ +include(${CMAKE_CURRENT_LIST_DIR}/../Sbom/MultiSetSingleSbom.cmake) + +install(EXPORT setA NAMESPACE A:: DESTINATION lib/cmake/A) +install(EXPORT setB NAMESPACE B:: DESTINATION lib/cmake/B) +install(SBOM mySbom EXPORTS setA setB DESTINATION .) diff --git a/Tests/RunCMake/InstallSbom/ProjectMetadata.cmake b/Tests/RunCMake/InstallSbom/ProjectMetadata.cmake index db94319adf..daaf3b47e5 100644 --- a/Tests/RunCMake/InstallSbom/ProjectMetadata.cmake +++ b/Tests/RunCMake/InstallSbom/ProjectMetadata.cmake @@ -7,6 +7,6 @@ install( HOMEPAGE_URL "www.example.com" PACKAGE_URL "https://example.com/test_targets.tar.gz" VERSION "1.3.4" - EXPORT test_targets + EXPORTS test_targets DESTINATION . ) diff --git a/Tests/RunCMake/InstallSbom/ReferencesNonExportedTarget-install-check.cmake b/Tests/RunCMake/InstallSbom/ReferencesNonExportedTarget-install-check.cmake deleted file mode 100644 index 6dc7558f60..0000000000 --- a/Tests/RunCMake/InstallSbom/ReferencesNonExportedTarget-install-check.cmake +++ /dev/null @@ -1,2 +0,0 @@ -file(READ "${RunCMake_TEST_INSTALL_DIR}/dog.spdx.json" content) -include(${CMAKE_CURRENT_LIST_DIR}/../Sbom/ReferencesNonExportedTarget-install-check.cmake) diff --git a/Tests/RunCMake/InstallSbom/ReferencesNonExportedTarget-result.txt b/Tests/RunCMake/InstallSbom/ReferencesNonExportedTarget-result.txt new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/Tests/RunCMake/InstallSbom/ReferencesNonExportedTarget-result.txt @@ -0,0 +1 @@ +1 diff --git a/Tests/RunCMake/InstallSbom/ReferencesNonExportedTarget-stderr.txt b/Tests/RunCMake/InstallSbom/ReferencesNonExportedTarget-stderr.txt new file mode 100644 index 0000000000..3dfbdb65da --- /dev/null +++ b/Tests/RunCMake/InstallSbom/ReferencesNonExportedTarget-stderr.txt @@ -0,0 +1,6 @@ +CMake Error in CMakeLists\.txt: + Target "canine" references target "mammal" which has no + install\(EXPORT\)/export\(EXPORT\) namespace and is not covered by any SBOM\. + An SBOM cannot attribute this dependency\. Give "mammal" an + install\(EXPORT\)/export\(EXPORT\) with a NAMESPACE, or include it in an + install\(SBOM\)/export\(SBOM\)\. diff --git a/Tests/RunCMake/InstallSbom/ReferencesNonExportedTarget.cmake b/Tests/RunCMake/InstallSbom/ReferencesNonExportedTarget.cmake index 0fcedfafd8..561529c27f 100644 --- a/Tests/RunCMake/InstallSbom/ReferencesNonExportedTarget.cmake +++ b/Tests/RunCMake/InstallSbom/ReferencesNonExportedTarget.cmake @@ -1,3 +1,3 @@ include(${CMAKE_CURRENT_LIST_DIR}/../Sbom/ReferencesNonExportedTarget.cmake) -install(SBOM dog EXPORT dog DESTINATION .) +install(SBOM dog EXPORTS dog DESTINATION .) diff --git a/Tests/RunCMake/InstallSbom/Requirements-install-check.cmake b/Tests/RunCMake/InstallSbom/Requirements-install-check.cmake index f70ce28abf..1c2f057763 100644 --- a/Tests/RunCMake/InstallSbom/Requirements-install-check.cmake +++ b/Tests/RunCMake/InstallSbom/Requirements-install-check.cmake @@ -1,3 +1,3 @@ -file(READ "${RunCMake_TEST_INSTALL_DIR}/bar.spdx.json" BAR_CONTENT) -file(READ "${RunCMake_TEST_INSTALL_DIR}/foo.spdx.json" FOO_CONTENT) +file(READ "${RunCMake_TEST_INSTALL_DIR}/bar_sbom.spdx.json" BAR_CONTENT) +file(READ "${RunCMake_TEST_INSTALL_DIR}/foo_sbom.spdx.json" FOO_CONTENT) include(${CMAKE_CURRENT_LIST_DIR}/../Sbom/Requirements-install-check.cmake) diff --git a/Tests/RunCMake/InstallSbom/Requirements.cmake b/Tests/RunCMake/InstallSbom/Requirements.cmake index a437e54e41..350fd858d1 100644 --- a/Tests/RunCMake/InstallSbom/Requirements.cmake +++ b/Tests/RunCMake/InstallSbom/Requirements.cmake @@ -1,4 +1 @@ include(${CMAKE_CURRENT_LIST_DIR}/../Sbom/Requirements.cmake) - -install(SBOM foo EXPORT foo DESTINATION .) -install(SBOM bar EXPORT bar DESTINATION .) diff --git a/Tests/RunCMake/InstallSbom/RunCMakeTest.cmake b/Tests/RunCMake/InstallSbom/RunCMakeTest.cmake index 895cc2622e..37b9381360 100644 --- a/Tests/RunCMake/InstallSbom/RunCMakeTest.cmake +++ b/Tests/RunCMake/InstallSbom/RunCMakeTest.cmake @@ -5,6 +5,15 @@ set(common_test_options "-DCMAKE_EXPERIMENTAL_GENERATE_SBOM:STRING=ca494ed3-b261-4205-a01f-603c95e4cae0" ) +function(run_cmake_error test) + set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/${test}-build) + set(RunCMake_TEST_OPTIONS ${common_test_options}) + if(NOT RunCMake_GENERATOR_IS_MULTI_CONFIG) + list(APPEND RunCMake_TEST_OPTIONS -DCMAKE_BUILD_TYPE=DEBUG) + endif() + run_cmake(${test}) +endfunction() + function(run_cmake_install test) set(extra_options ${ARGN}) set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/${test}-build) @@ -17,16 +26,28 @@ function(run_cmake_install test) run_cmake(${test}) set(RunCMake_TEST_NO_CLEAN TRUE) + set(RunCMake_TEST_OUTPUT_MERGE 1) run_cmake_command(${test}-build ${CMAKE_COMMAND} --build . --config Debug) + unset(RunCMake_TEST_OUTPUT_MERGE) run_cmake_command(${test}-install ${CMAKE_COMMAND} --install . --config Debug) endfunction() run_cmake_install(ApplicationTarget) +run_cmake_install(InstallExportPlusSbomSameSet) run_cmake_install(InterfaceTarget) run_cmake_install(SharedTarget) run_cmake_install(Requirements) +run_cmake_install(SbomNamespaceFallback) +run_cmake_install(MultiSetSingleSbom) +run_cmake_install(TargetInMultipleSets) +run_cmake_install(EmptyNamespaceFallback -Wauthor) +run_cmake_install(SbomNamespaceAmbiguity -Wauthor) run_cmake_install(IgnoresInterfaceDirs) run_cmake_install(MissingPackageNamespace) -run_cmake_install(ReferencesNonExportedTarget) run_cmake_install(ProjectMetadata) + +run_cmake_error(ReferencesNonExportedTarget) +run_cmake_error(MultiNamespaceAmbiguity) +run_cmake_error(MultiSetAmbiguity) +run_cmake_error(DuplicateSbom) diff --git a/Tests/RunCMake/InstallSbom/SbomNamespaceAmbiguity-install-check.cmake b/Tests/RunCMake/InstallSbom/SbomNamespaceAmbiguity-install-check.cmake new file mode 100644 index 0000000000..c35bcc07b2 --- /dev/null +++ b/Tests/RunCMake/InstallSbom/SbomNamespaceAmbiguity-install-check.cmake @@ -0,0 +1,2 @@ +file(READ "${RunCMake_TEST_INSTALL_DIR}/bar_sbom.spdx.json" BAR_CONTENT) +include(${CMAKE_CURRENT_LIST_DIR}/../Sbom/SbomNamespaceAmbiguity-install-check.cmake) diff --git a/Tests/RunCMake/InstallSbom/SbomNamespaceAmbiguity-stderr.txt b/Tests/RunCMake/InstallSbom/SbomNamespaceAmbiguity-stderr.txt new file mode 100644 index 0000000000..ea4234ff52 --- /dev/null +++ b/Tests/RunCMake/InstallSbom/SbomNamespaceAmbiguity-stderr.txt @@ -0,0 +1,4 @@ +CMake Warning \(author\) in CMakeLists\.txt: + Target "libc" references target "libb" which has no + install\(EXPORT\)/export\(EXPORT\) namespace and is covered by multiple SBOMs: + foo_sbom, foo_sbom2\. Attributing to "foo_sbom" \(first alphabetically\)\. diff --git a/Tests/RunCMake/InstallSbom/SbomNamespaceAmbiguity.cmake b/Tests/RunCMake/InstallSbom/SbomNamespaceAmbiguity.cmake new file mode 100644 index 0000000000..564979dc48 --- /dev/null +++ b/Tests/RunCMake/InstallSbom/SbomNamespaceAmbiguity.cmake @@ -0,0 +1,5 @@ +include(${CMAKE_CURRENT_LIST_DIR}/../Sbom/SbomNamespaceAmbiguity.cmake) + +install(SBOM foo_sbom EXPORTS foo DESTINATION .) +install(SBOM foo_sbom2 EXPORTS foo DESTINATION .) +install(SBOM bar_sbom EXPORTS bar DESTINATION .) diff --git a/Tests/RunCMake/InstallSbom/SbomNamespaceFallback-install-check.cmake b/Tests/RunCMake/InstallSbom/SbomNamespaceFallback-install-check.cmake new file mode 100644 index 0000000000..dbbec2d16c --- /dev/null +++ b/Tests/RunCMake/InstallSbom/SbomNamespaceFallback-install-check.cmake @@ -0,0 +1,3 @@ +file(READ "${RunCMake_TEST_INSTALL_DIR}/bar.spdx.json" BAR_CONTENT) +file(READ "${RunCMake_TEST_INSTALL_DIR}/foo.spdx.json" FOO_CONTENT) +include(${CMAKE_CURRENT_LIST_DIR}/../Sbom/SbomNamespaceFallback-install-check.cmake) diff --git a/Tests/RunCMake/InstallSbom/SbomNamespaceFallback.cmake b/Tests/RunCMake/InstallSbom/SbomNamespaceFallback.cmake new file mode 100644 index 0000000000..132e9c9d61 --- /dev/null +++ b/Tests/RunCMake/InstallSbom/SbomNamespaceFallback.cmake @@ -0,0 +1,4 @@ +include(${CMAKE_CURRENT_LIST_DIR}/../Sbom/SbomNamespaceFallback.cmake) + +install(SBOM foo EXPORTS foo DESTINATION .) +install(SBOM bar EXPORTS bar DESTINATION .) diff --git a/Tests/RunCMake/InstallSbom/SharedTarget.cmake b/Tests/RunCMake/InstallSbom/SharedTarget.cmake index 63ba24991e..c3ee8f00f3 100644 --- a/Tests/RunCMake/InstallSbom/SharedTarget.cmake +++ b/Tests/RunCMake/InstallSbom/SharedTarget.cmake @@ -2,6 +2,6 @@ include(${CMAKE_CURRENT_LIST_DIR}/../Sbom/SharedTarget.cmake) install( SBOM shared_targets - EXPORT shared_targets + EXPORTS shared_targets DESTINATION . ) diff --git a/Tests/RunCMake/InstallSbom/TargetInMultipleSets-install-check.cmake b/Tests/RunCMake/InstallSbom/TargetInMultipleSets-install-check.cmake new file mode 100644 index 0000000000..1d04796df9 --- /dev/null +++ b/Tests/RunCMake/InstallSbom/TargetInMultipleSets-install-check.cmake @@ -0,0 +1,2 @@ +file(READ "${RunCMake_TEST_INSTALL_DIR}/mySbom.spdx.json" content) +include(${CMAKE_CURRENT_LIST_DIR}/../Sbom/TargetInMultipleSets-install-check.cmake) diff --git a/Tests/RunCMake/InstallSbom/TargetInMultipleSets.cmake b/Tests/RunCMake/InstallSbom/TargetInMultipleSets.cmake new file mode 100644 index 0000000000..20ced6a928 --- /dev/null +++ b/Tests/RunCMake/InstallSbom/TargetInMultipleSets.cmake @@ -0,0 +1,3 @@ +include(${CMAKE_CURRENT_LIST_DIR}/../Sbom/TargetInMultipleSets.cmake) + +install(SBOM mySbom EXPORTS setA1 setA2 DESTINATION .) diff --git a/Tests/RunCMake/Sbom/DuplicateSbom.cmake b/Tests/RunCMake/Sbom/DuplicateSbom.cmake new file mode 100644 index 0000000000..b44eb0859d --- /dev/null +++ b/Tests/RunCMake/Sbom/DuplicateSbom.cmake @@ -0,0 +1,6 @@ +include(${CMAKE_CURRENT_LIST_DIR}/Setup.cmake) +include(GNUInstallDirs) + +add_library(liba INTERFACE) + +install(TARGETS liba EXPORT setA DESTINATION .) diff --git a/Tests/RunCMake/Sbom/EmptyNamespaceFallback-install-check.cmake b/Tests/RunCMake/Sbom/EmptyNamespaceFallback-install-check.cmake new file mode 100644 index 0000000000..dd5a6b1613 --- /dev/null +++ b/Tests/RunCMake/Sbom/EmptyNamespaceFallback-install-check.cmake @@ -0,0 +1,48 @@ +include(${CMAKE_CURRENT_LIST_DIR}/Assertions.cmake) + +set(SPDX_DOCUMENT_EXPECTED [=[ +{ + "spdxId" : "urn:mySbom#SPDXDocument", + "name" : "mySbom", + "type" : "SpdxDocument" +} +]=]) + +set(LIBB_EXPECTED [=[ +{ + "spdxId" : "urn:libb#Package", + "name" : "libb", + "software_primaryPurpose" : "library", + "type" : "software_Package" +} +]=]) + +set(LIBA_BARE_EXPECTED [=[ +{ + "spdxId" : "urn:liba#Package", + "name" : "liba", + "type" : "software_Package" +} +]=]) + +set(LIBB_BUILD_REQUIRES_EXPECTED [=[ +{ + "description" : "Required Build-Time Libraries", + "from" : "urn:libb#Package", + "relationshipType" : "dependsOn", + "spdxId" : "urn:Shared#Relationship", + "to" : + [ + "urn:liba#Package" + ], + "type" : "Relationship" +} +]=]) + +string(JSON SPDX_DOCUMENT GET "${content}" "@graph" "1") +expect_object("${SPDX_DOCUMENT}" SPDX_DOCUMENT_EXPECTED) +expect_array("${SPDX_DOCUMENT}" 1 "rootElement") +expect_object("${SPDX_DOCUMENT}" LIBB_EXPECTED "rootElement") +expect_object("${SPDX_DOCUMENT}" LIBA_BARE_EXPECTED "element") +string(JSON BUILD_REQUIRES GET "${content}" "@graph" "2") +expect_object("${BUILD_REQUIRES}" LIBB_BUILD_REQUIRES_EXPECTED) diff --git a/Tests/RunCMake/Sbom/EmptyNamespaceFallback.cmake b/Tests/RunCMake/Sbom/EmptyNamespaceFallback.cmake new file mode 100644 index 0000000000..e0719d2ff6 --- /dev/null +++ b/Tests/RunCMake/Sbom/EmptyNamespaceFallback.cmake @@ -0,0 +1,9 @@ +include(${CMAKE_CURRENT_LIST_DIR}/Setup.cmake) +include(GNUInstallDirs) + +add_library(liba INTERFACE) +add_library(libb INTERFACE) +target_link_libraries(libb INTERFACE liba) + +install(TARGETS liba EXPORT setA DESTINATION .) +install(TARGETS libb EXPORT setB DESTINATION .) diff --git a/Tests/RunCMake/Sbom/InstallExportPlusSbomSameSet.cmake b/Tests/RunCMake/Sbom/InstallExportPlusSbomSameSet.cmake new file mode 100644 index 0000000000..1268fbe8d1 --- /dev/null +++ b/Tests/RunCMake/Sbom/InstallExportPlusSbomSameSet.cmake @@ -0,0 +1,12 @@ +include(${CMAKE_CURRENT_LIST_DIR}/Setup.cmake) +include(GNUInstallDirs) + +# Guards against the issue described here: +# https://gitlab.kitware.com/cmake/cmake/-/work_items/27721 + +add_library(liba INTERFACE) +add_library(libb INTERFACE) +target_link_libraries(libb INTERFACE liba) + +install(TARGETS liba EXPORT export_set_a DESTINATION .) +install(TARGETS libb EXPORT export_set_b DESTINATION .) diff --git a/Tests/RunCMake/Sbom/MultiNamespaceAmbiguity.cmake b/Tests/RunCMake/Sbom/MultiNamespaceAmbiguity.cmake new file mode 100644 index 0000000000..e0719d2ff6 --- /dev/null +++ b/Tests/RunCMake/Sbom/MultiNamespaceAmbiguity.cmake @@ -0,0 +1,9 @@ +include(${CMAKE_CURRENT_LIST_DIR}/Setup.cmake) +include(GNUInstallDirs) + +add_library(liba INTERFACE) +add_library(libb INTERFACE) +target_link_libraries(libb INTERFACE liba) + +install(TARGETS liba EXPORT setA DESTINATION .) +install(TARGETS libb EXPORT setB DESTINATION .) diff --git a/Tests/RunCMake/Sbom/MultiSetAmbiguity.cmake b/Tests/RunCMake/Sbom/MultiSetAmbiguity.cmake new file mode 100644 index 0000000000..eab71b1f0f --- /dev/null +++ b/Tests/RunCMake/Sbom/MultiSetAmbiguity.cmake @@ -0,0 +1,10 @@ +include(${CMAKE_CURRENT_LIST_DIR}/Setup.cmake) +include(GNUInstallDirs) + +add_library(liba INTERFACE) +add_library(libb INTERFACE) +target_link_libraries(libb INTERFACE liba) + +install(TARGETS liba EXPORT setA1 DESTINATION .) +install(TARGETS liba EXPORT setA2 DESTINATION .) +install(TARGETS libb EXPORT setB DESTINATION .) diff --git a/Tests/RunCMake/Sbom/MultiSetSingleSbom-install-check.cmake b/Tests/RunCMake/Sbom/MultiSetSingleSbom-install-check.cmake new file mode 100644 index 0000000000..50ac5a52b1 --- /dev/null +++ b/Tests/RunCMake/Sbom/MultiSetSingleSbom-install-check.cmake @@ -0,0 +1,49 @@ +include(${CMAKE_CURRENT_LIST_DIR}/Assertions.cmake) + +set(SPDX_DOCUMENT_EXPECTED [=[ +{ + "spdxId" : "urn:mySbom#SPDXDocument", + "name" : "mySbom", + "type" : "SpdxDocument" +} +]=]) + +set(LIBA_EXPECTED [=[ +{ + "spdxId" : "urn:liba#Package", + "name" : "liba", + "software_primaryPurpose" : "library", + "type" : "software_Package" +} +]=]) + +set(LIBB_EXPECTED [=[ +{ + "spdxId" : "urn:libb#Package", + "name" : "libb", + "software_primaryPurpose" : "library", + "type" : "software_Package" +} +]=]) + +set(LIBB_LINKED_LIBRARIES_EXPECTED [=[ +{ + "description" : "Linked Libraries", + "from" : "urn:libb#Package", + "relationshipType" : "dependsOn", + "to" : + [ + "urn:liba#Package" + ], + "type" : "Relationship" +} +]=]) + +expect_value("${content}" "https://spdx.org/rdf/3.0.1/spdx-context.jsonld" "@context") +string(JSON SPDX_DOCUMENT GET "${content}" "@graph" "1") +expect_object("${SPDX_DOCUMENT}" SPDX_DOCUMENT_EXPECTED) +expect_array("${SPDX_DOCUMENT}" 2 "rootElement") +expect_object("${SPDX_DOCUMENT}" LIBA_EXPECTED "rootElement") +expect_object("${SPDX_DOCUMENT}" LIBB_EXPECTED "rootElement") +string(JSON LINKED_LIBRARIES GET "${content}" "@graph" "2") +expect_object("${LINKED_LIBRARIES}" LIBB_LINKED_LIBRARIES_EXPECTED) diff --git a/Tests/RunCMake/Sbom/MultiSetSingleSbom.cmake b/Tests/RunCMake/Sbom/MultiSetSingleSbom.cmake new file mode 100644 index 0000000000..e0719d2ff6 --- /dev/null +++ b/Tests/RunCMake/Sbom/MultiSetSingleSbom.cmake @@ -0,0 +1,9 @@ +include(${CMAKE_CURRENT_LIST_DIR}/Setup.cmake) +include(GNUInstallDirs) + +add_library(liba INTERFACE) +add_library(libb INTERFACE) +target_link_libraries(libb INTERFACE liba) + +install(TARGETS liba EXPORT setA DESTINATION .) +install(TARGETS libb EXPORT setB DESTINATION .) diff --git a/Tests/RunCMake/Sbom/ReferencesNonExportedTarget-install-check.cmake b/Tests/RunCMake/Sbom/ReferencesNonExportedTarget-install-check.cmake deleted file mode 100644 index 2018f850a8..0000000000 --- a/Tests/RunCMake/Sbom/ReferencesNonExportedTarget-install-check.cmake +++ /dev/null @@ -1,72 +0,0 @@ -include(${CMAKE_CURRENT_LIST_DIR}/Assertions.cmake) - -set(CREATION_INFO_EXPECTED [=[ -{ - "@id": "_:Build#CreationInfo", - "comment": "This SBOM was generated from the CMakeLists.txt File", - "createdBy": - [ - "https://gitlab.kitware.com/cmake/cmake" - ], - "specVersion": "3.0.1", - "type": "CreationInfo" -} -]=]) - -set(SPDX_DOCUMENT_EXPECTED [=[ -{ - "name" : "dog", - "profileConformance" : - [ - "core", - "software" - ], - "creationInfo" : "_:Build#CreationInfo", - "spdxId" : "urn:dog#SPDXDocument", - "type" : "SpdxDocument" -} -]=]) - -set(APPLICATION_EXPECTED [=[ -{ - "spdxId" : "urn:canine#Package", - "name" : "canine", - "software_primaryPurpose" : "library", - "type" : "software_Package" -} -]=]) - -set(DEPENDENCY_EXPECTED [=[ -{ - "spdxId" : "urn:mammal#Package", - "name" : "mammal", - "type" : "software_Package" -} -]=]) - -set(BUILD_LINKED_LIBRARIES_EXPECTED [=[ -{ - "creationInfo" : "_:Build#CreationInfo", - "description" : "Required Build-Time Libraries", - "from" : "urn:canine#Package", - "relationshipType" : "dependsOn", - "spdxId" : "urn:Shared#Relationship", - "to" : - [ - "urn:mammal#Package" - ], - "type" : "Relationship" -} -]=]) - - -expect_value("${content}" "https://spdx.org/rdf/3.0.1/spdx-context.jsonld" "@context") -string(JSON CREATION_INFO GET "${content}" "@graph" "0") -expect_object("${CREATION_INFO}" CREATION_INFO_EXPECTED) - -string(JSON SPDX_DOCUMENT GET "${content}" "@graph" "1") -expect_object("${SPDX_DOCUMENT}" SPDX_DOCUMENT_EXPECTED) -expect_object("${SPDX_DOCUMENT}" APPLICATION_EXPECTED "rootElement") -expect_object("${SPDX_DOCUMENT}" DEPENDENCY_EXPECTED "element") -string(JSON LINKED_LIBRARIES GET "${content}" "@graph" "2") -expect_object("${LINKED_LIBRARIES}" BUILD_LINKED_LIBRARIES_EXPECTED) diff --git a/Tests/RunCMake/Sbom/Requirements-install-check.cmake b/Tests/RunCMake/Sbom/Requirements-install-check.cmake index 5c9fa0d71b..92f2010239 100644 --- a/Tests/RunCMake/Sbom/Requirements-install-check.cmake +++ b/Tests/RunCMake/Sbom/Requirements-install-check.cmake @@ -15,30 +15,30 @@ set(CREATION_INFO [=[ ]=]) -set(BAR_SPDX_DOCUMENT [=[ +set(BAR_SPDX_DOCUMENT_EXPECTED [=[ { "creationInfo" : "_:Build#CreationInfo", - "name" : "bar", + "name" : "bar_sbom", "profileConformance" : [ "core", "software" ], - "spdxId" : "urn:bar#SPDXDocument", + "spdxId" : "urn:bar_sbom#SPDXDocument", "type" : "SpdxDocument" } ]=]) -set(FOO_SPDX_DOCUMENT [=[ +set(FOO_SPDX_DOCUMENT_EXPECTED [=[ { "creationInfo" : "_:Build#CreationInfo", - "name" : "foo", + "name" : "foo_sbom", "profileConformance" : [ "core", "software" ], - "spdxId" : "urn:foo#SPDXDocument", + "spdxId" : "urn:foo_sbom#SPDXDocument", "type" : "SpdxDocument" } ]=]) @@ -136,13 +136,13 @@ set(CREATION_INFO_EXPECTED [=[ expect_value("${FOO_CONTENT}" "https://spdx.org/rdf/3.0.1/spdx-context.jsonld" "@context") string(JSON FOO_CREATION_INFO GET "${FOO_CONTENT}" "@graph" "0") string(JSON FOO_SPDX_DOCUMENT GET "${FOO_CONTENT}" "@graph" "1") -expect_object("${FOO_SPDX_DOCUMENT}" FOO_SPDX_DOCUMENT) +expect_object("${FOO_SPDX_DOCUMENT}" FOO_SPDX_DOCUMENT_EXPECTED) expect_object("${FOO_SPDX_DOCUMENT}" FOO_LIBB "rootElement") expect_value("${BAR_CONTENT}" "https://spdx.org/rdf/3.0.1/spdx-context.jsonld" "@context") string(JSON BAR_CREATION_INFO GET "${BAR_CONTENT}" "@graph" "0") string(JSON BAR_SPDX_DOCUMENT GET "${BAR_CONTENT}" "@graph" "1") -expect_object("${BAR_SPDX_DOCUMENT}" BAR_SPDX_DOCUMENT) +expect_object("${BAR_SPDX_DOCUMENT}" BAR_SPDX_DOCUMENT_EXPECTED) expect_object("${BAR_SPDX_DOCUMENT}" BAR_LIBC "rootElement") expect_object("${BAR_SPDX_DOCUMENT}" BAR_LIBD "rootElement") expect_object("${BAR_SPDX_DOCUMENT}" BAR_DEPENDENCY_TEST "element") diff --git a/Tests/RunCMake/Sbom/Requirements.cmake b/Tests/RunCMake/Sbom/Requirements.cmake index 1750fc2496..5f3e4d7458 100644 --- a/Tests/RunCMake/Sbom/Requirements.cmake +++ b/Tests/RunCMake/Sbom/Requirements.cmake @@ -19,7 +19,8 @@ add_library(bar ALIAS libc) target_link_libraries(libd INTERFACE test::liba foo bar) install(TARGETS libb EXPORT foo DESTINATION .) -install(SBOM foo EXPORT foo DESTINATION .) +install(EXPORT foo NAMESPACE foo:: DESTINATION lib/cmake/foo) +install(SBOM foo_sbom EXPORTS foo DESTINATION .) install(TARGETS libc libd EXPORT bar DESTINATION .) -install(SBOM bar EXPORT bar DESTINATION .) +install(SBOM bar_sbom EXPORTS bar DESTINATION .) diff --git a/Tests/RunCMake/Sbom/SbomNamespaceAmbiguity-install-check.cmake b/Tests/RunCMake/Sbom/SbomNamespaceAmbiguity-install-check.cmake new file mode 100644 index 0000000000..47d12bca2f --- /dev/null +++ b/Tests/RunCMake/Sbom/SbomNamespaceAmbiguity-install-check.cmake @@ -0,0 +1,49 @@ +include(${CMAKE_CURRENT_LIST_DIR}/Assertions.cmake) + +set(BAR_SPDX_DOCUMENT_EXPECTED [=[ +{ + "creationInfo" : "_:Build#CreationInfo", + "name" : "bar_sbom", + "profileConformance" : + [ + "core", + "software" + ], + "spdxId" : "urn:bar_sbom#SPDXDocument", + "type" : "SpdxDocument" +} +]=]) + +set(BAR_LIBC [=[ +{ + "creationInfo" : "_:Build#CreationInfo", + "name" : "libc", + "software_primaryPurpose" : "library", + "spdxId" : "urn:libc#Package", + "type" : "software_Package" +} +]=]) + +set(BAR_DEPENDENCY_FOO [=[ +{ + "creationInfo" : "_:Build#CreationInfo", + "name" : "foo_sbom:libb", + "originatedBy" : + [ + { + "creationInfo" : "_:Build#CreationInfo", + "name" : "foo_sbom", + "spdxId" : "urn:foo_sbom#Organization", + "type" : "Organization" + } + ], + "spdxId" : "urn:foo_sbom:libb#Package", + "type" : "software_Package" +} +]=]) + +expect_value("${BAR_CONTENT}" "https://spdx.org/rdf/3.0.1/spdx-context.jsonld" "@context") +string(JSON BAR_SPDX_DOCUMENT GET "${BAR_CONTENT}" "@graph" "1") +expect_object("${BAR_SPDX_DOCUMENT}" BAR_SPDX_DOCUMENT_EXPECTED) +expect_object("${BAR_SPDX_DOCUMENT}" BAR_LIBC "rootElement") +expect_object("${BAR_SPDX_DOCUMENT}" BAR_DEPENDENCY_FOO "element") diff --git a/Tests/RunCMake/Sbom/SbomNamespaceAmbiguity.cmake b/Tests/RunCMake/Sbom/SbomNamespaceAmbiguity.cmake new file mode 100644 index 0000000000..ba1be45615 --- /dev/null +++ b/Tests/RunCMake/Sbom/SbomNamespaceAmbiguity.cmake @@ -0,0 +1,9 @@ +include(${CMAKE_CURRENT_LIST_DIR}/Setup.cmake) + +add_library(libb INTERFACE) +add_library(libc INTERFACE) + +target_link_libraries(libc INTERFACE libb) + +install(TARGETS libb EXPORT foo DESTINATION .) +install(TARGETS libc EXPORT bar DESTINATION .) diff --git a/Tests/RunCMake/Sbom/SbomNamespaceFallback-install-check.cmake b/Tests/RunCMake/Sbom/SbomNamespaceFallback-install-check.cmake new file mode 100644 index 0000000000..d80856cb72 --- /dev/null +++ b/Tests/RunCMake/Sbom/SbomNamespaceFallback-install-check.cmake @@ -0,0 +1,78 @@ +include(${CMAKE_CURRENT_LIST_DIR}/Assertions.cmake) + +set(FOO_SPDX_DOCUMENT_EXPECTED [=[ +{ + "creationInfo" : "_:Build#CreationInfo", + "name" : "foo", + "profileConformance" : + [ + "core", + "software" + ], + "spdxId" : "urn:foo#SPDXDocument", + "type" : "SpdxDocument" +} +]=]) + +set(BAR_SPDX_DOCUMENT_EXPECTED [=[ +{ + "creationInfo" : "_:Build#CreationInfo", + "name" : "bar", + "profileConformance" : + [ + "core", + "software" + ], + "spdxId" : "urn:bar#SPDXDocument", + "type" : "SpdxDocument" +} +]=]) + +set(FOO_LIBB [=[ +{ + "creationInfo" : "_:Build#CreationInfo", + "name" : "libb", + "software_primaryPurpose" : "library", + "spdxId" : "urn:libb#Package", + "type" : "software_Package" +} +]=]) + +set(BAR_LIBC [=[ +{ + "creationInfo" : "_:Build#CreationInfo", + "name" : "libc", + "software_primaryPurpose" : "library", + "spdxId" : "urn:libc#Package", + "type" : "software_Package" +} +]=]) + +set(BAR_DEPENDENCY_FOO [=[ +{ + "creationInfo" : "_:Build#CreationInfo", + "name" : "foo:libb", + "originatedBy" : + [ + { + "creationInfo" : "_:Build#CreationInfo", + "name" : "foo", + "spdxId" : "urn:foo#Organization", + "type" : "Organization" + } + ], + "spdxId" : "urn:foo:libb#Package", + "type" : "software_Package" +} +]=]) + +expect_value("${FOO_CONTENT}" "https://spdx.org/rdf/3.0.1/spdx-context.jsonld" "@context") +string(JSON FOO_SPDX_DOCUMENT GET "${FOO_CONTENT}" "@graph" "1") +expect_object("${FOO_SPDX_DOCUMENT}" FOO_SPDX_DOCUMENT_EXPECTED) +expect_object("${FOO_SPDX_DOCUMENT}" FOO_LIBB "rootElement") + +expect_value("${BAR_CONTENT}" "https://spdx.org/rdf/3.0.1/spdx-context.jsonld" "@context") +string(JSON BAR_SPDX_DOCUMENT GET "${BAR_CONTENT}" "@graph" "1") +expect_object("${BAR_SPDX_DOCUMENT}" BAR_SPDX_DOCUMENT_EXPECTED) +expect_object("${BAR_SPDX_DOCUMENT}" BAR_LIBC "rootElement") +expect_object("${BAR_SPDX_DOCUMENT}" BAR_DEPENDENCY_FOO "element") diff --git a/Tests/RunCMake/Sbom/SbomNamespaceFallback.cmake b/Tests/RunCMake/Sbom/SbomNamespaceFallback.cmake new file mode 100644 index 0000000000..ba1be45615 --- /dev/null +++ b/Tests/RunCMake/Sbom/SbomNamespaceFallback.cmake @@ -0,0 +1,9 @@ +include(${CMAKE_CURRENT_LIST_DIR}/Setup.cmake) + +add_library(libb INTERFACE) +add_library(libc INTERFACE) + +target_link_libraries(libc INTERFACE libb) + +install(TARGETS libb EXPORT foo DESTINATION .) +install(TARGETS libc EXPORT bar DESTINATION .) diff --git a/Tests/RunCMake/Sbom/TargetInMultipleSets-install-check.cmake b/Tests/RunCMake/Sbom/TargetInMultipleSets-install-check.cmake new file mode 100644 index 0000000000..76c00f7891 --- /dev/null +++ b/Tests/RunCMake/Sbom/TargetInMultipleSets-install-check.cmake @@ -0,0 +1,24 @@ +include(${CMAKE_CURRENT_LIST_DIR}/Assertions.cmake) + +set(SPDX_DOCUMENT_EXPECTED [=[ +{ + "spdxId" : "urn:mySbom#SPDXDocument", + "name" : "mySbom", + "type" : "SpdxDocument" +} +]=]) + +set(LIBA_EXPECTED [=[ +{ + "spdxId" : "urn:liba#Package", + "name" : "liba", + "software_primaryPurpose" : "library", + "type" : "software_Package" +} +]=]) + +string(JSON SPDX_DOCUMENT GET "${content}" "@graph" "1") +expect_object("${SPDX_DOCUMENT}" SPDX_DOCUMENT_EXPECTED) +# liba should only appear once. +expect_array("${SPDX_DOCUMENT}" 1 "rootElement") +expect_object("${SPDX_DOCUMENT}" LIBA_EXPECTED "rootElement") diff --git a/Tests/RunCMake/Sbom/TargetInMultipleSets.cmake b/Tests/RunCMake/Sbom/TargetInMultipleSets.cmake new file mode 100644 index 0000000000..ba1986cff9 --- /dev/null +++ b/Tests/RunCMake/Sbom/TargetInMultipleSets.cmake @@ -0,0 +1,7 @@ +include(${CMAKE_CURRENT_LIST_DIR}/Setup.cmake) +include(GNUInstallDirs) + +add_library(liba INTERFACE) + +install(TARGETS liba EXPORT setA1 DESTINATION .) +install(TARGETS liba EXPORT setA2 DESTINATION .) From 2900aa29115b1b29b1423a3e9cff81836ea41203 Mon Sep 17 00:00:00 2001 From: Tom Osika Date: Thu, 28 May 2026 20:29:35 -0400 Subject: [PATCH 4/4] SBOM: Update experimental UUID --- Help/dev/experimental.rst | 2 +- Source/cmExperimental.cxx | 2 +- Tests/RunCMake/EnvSbom/RunCMakeTest.cmake | 2 +- Tests/RunCMake/ExportSbom/RunCMakeTest.cmake | 2 +- Tests/RunCMake/InstallSbom/RunCMakeTest.cmake | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Help/dev/experimental.rst b/Help/dev/experimental.rst index 61b77c02dc..77de70991d 100644 --- a/Help/dev/experimental.rst +++ b/Help/dev/experimental.rst @@ -109,7 +109,7 @@ In order to activate support for the :command:`install(SBOM)` command, set * variable ``CMAKE_EXPERIMENTAL_GENERATE_SBOM`` to -* value ``ca494ed3-b261-4205-a01f-603c95e4cae0``. +* value ``2d856d6d-53e8-488b-a17f-d486d2cac317``. This UUID may change in future versions of CMake. Be sure to use the value documented here by the source tree of the version of CMake with which you are diff --git a/Source/cmExperimental.cxx b/Source/cmExperimental.cxx index cf3ea5e971..b9b29e8787 100644 --- a/Source/cmExperimental.cxx +++ b/Source/cmExperimental.cxx @@ -55,7 +55,7 @@ cmExperimental::FeatureData const LookupTable[] = { {}, cmExperimental::TryCompileCondition::Never }, { "GenerateSbom", - "ca494ed3-b261-4205-a01f-603c95e4cae0", + "2d856d6d-53e8-488b-a17f-d486d2cac317", "CMAKE_EXPERIMENTAL_GENERATE_SBOM", "CMake's support for generating software bill of materials (Sbom) " "information in SPDX format is experimental. It is meant only for " diff --git a/Tests/RunCMake/EnvSbom/RunCMakeTest.cmake b/Tests/RunCMake/EnvSbom/RunCMakeTest.cmake index e860160ac7..a188fcc739 100644 --- a/Tests/RunCMake/EnvSbom/RunCMakeTest.cmake +++ b/Tests/RunCMake/EnvSbom/RunCMakeTest.cmake @@ -2,7 +2,7 @@ include(RunCMake) set(common_test_options -Wno-author - "-DCMAKE_EXPERIMENTAL_GENERATE_SBOM:STRING=ca494ed3-b261-4205-a01f-603c95e4cae0" + "-DCMAKE_EXPERIMENTAL_GENERATE_SBOM:STRING=2d856d6d-53e8-488b-a17f-d486d2cac317" "-DCMAKE_INSTALL_SBOM_FORMATS:STRING=JSON" "-DCMAKE_INSTALL_LIBDIR=lib" ) diff --git a/Tests/RunCMake/ExportSbom/RunCMakeTest.cmake b/Tests/RunCMake/ExportSbom/RunCMakeTest.cmake index 25d2998059..244b34945e 100644 --- a/Tests/RunCMake/ExportSbom/RunCMakeTest.cmake +++ b/Tests/RunCMake/ExportSbom/RunCMakeTest.cmake @@ -2,7 +2,7 @@ include(RunCMake) set(common_test_options -Wno-author - "-DCMAKE_EXPERIMENTAL_GENERATE_SBOM:STRING=ca494ed3-b261-4205-a01f-603c95e4cae0" + "-DCMAKE_EXPERIMENTAL_GENERATE_SBOM:STRING=2d856d6d-53e8-488b-a17f-d486d2cac317" ) function(run_cmake_error test) diff --git a/Tests/RunCMake/InstallSbom/RunCMakeTest.cmake b/Tests/RunCMake/InstallSbom/RunCMakeTest.cmake index 37b9381360..fcf9986d9a 100644 --- a/Tests/RunCMake/InstallSbom/RunCMakeTest.cmake +++ b/Tests/RunCMake/InstallSbom/RunCMakeTest.cmake @@ -2,7 +2,7 @@ include(RunCMake) set(common_test_options -Wno-author - "-DCMAKE_EXPERIMENTAL_GENERATE_SBOM:STRING=ca494ed3-b261-4205-a01f-603c95e4cae0" + "-DCMAKE_EXPERIMENTAL_GENERATE_SBOM:STRING=2d856d6d-53e8-488b-a17f-d486d2cac317" ) function(run_cmake_error test)