From 273fc4cc49b993d643d79385025e51b42f6ac988 Mon Sep 17 00:00:00 2001 From: Tom Osika Date: Mon, 13 Apr 2026 21:29:06 -0400 Subject: [PATCH] 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 .)