Coverage Report

Created: 2026-04-29 07:01

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/CMake/Source/cmExportSbomGenerator.cxx
Line
Count
Source
1
/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
2
   file LICENSE.rst or https://cmake.org/licensing for details.  */
3
#include "cmExportSbomGenerator.h"
4
5
#include <array>
6
#include <map>
7
#include <memory>
8
#include <set>
9
#include <string>
10
#include <utility>
11
#include <vector>
12
13
#include <cm/optional>
14
#include <cmext/algorithm>
15
16
#include "cmArgumentParserTypes.h"
17
#include "cmDiagnostics.h"
18
#include "cmFindPackageStack.h"
19
#include "cmGeneratorExpression.h"
20
#include "cmGeneratorTarget.h"
21
#include "cmList.h"
22
#include "cmMakefile.h"
23
#include "cmSbomArguments.h"
24
#include "cmSbomObject.h"
25
#include "cmSpdx.h"
26
#include "cmSpdxSerializer.h"
27
#include "cmStateTypes.h"
28
#include "cmStringAlgorithms.h"
29
#include "cmSystemTools.h"
30
#include "cmTarget.h"
31
#include "cmValue.h"
32
33
cmSpdxPackage::PurposeId GetPurpose(cmStateEnums::TargetType type)
34
0
{
35
0
  switch (type) {
36
0
    case cmStateEnums::TargetType::EXECUTABLE:
37
0
      return cmSpdxPackage::PurposeId::APPLICATION;
38
0
    case cmStateEnums::TargetType::STATIC_LIBRARY:
39
0
    case cmStateEnums::TargetType::SHARED_LIBRARY:
40
0
    case cmStateEnums::TargetType::MODULE_LIBRARY:
41
0
    case cmStateEnums::TargetType::OBJECT_LIBRARY:
42
0
    case cmStateEnums::TargetType::INTERFACE_LIBRARY:
43
0
      return cmSpdxPackage::PurposeId::LIBRARY;
44
0
    case cmStateEnums::TargetType::UTILITY:
45
0
      return cmSpdxPackage::PurposeId::SOURCE;
46
0
    case cmStateEnums::TargetType::GLOBAL_TARGET:
47
0
    case cmStateEnums::TargetType::UNKNOWN_LIBRARY:
48
0
    default:
49
0
      return cmSpdxPackage::PurposeId::ARCHIVE;
50
0
  }
51
0
}
52
53
cmExportSbomGenerator::cmExportSbomGenerator(cmSbomArguments args)
54
0
  : PackageName(std::move(args.PackageName))
55
0
  , PackageVersion(std::move(args.Version))
56
0
  , PackageDescription(std::move(args.Description))
57
0
  , PackageWebsite(std::move(args.Website))
58
0
  , PackageLicense(std::move(args.License))
59
0
  , PackageFormat(args.GetFormat())
60
0
{
61
0
}
62
63
bool cmExportSbomGenerator::GenerateImportFile(std::ostream& os)
64
0
{
65
0
  return this->GenerateMainFile(os);
66
0
}
67
68
void cmExportSbomGenerator::WriteSbom(cmSbomDocument& doc,
69
                                      std::ostream& os) const
70
0
{
71
0
  switch (this->PackageFormat) {
72
0
    case cmSbomArguments::SbomFormat::SPDX_3_0_JSON:
73
0
      cmSpdxSerializer{}.WriteSbom(os, cmSbomObject(doc));
74
0
      break;
75
0
    case cmSbomArguments::SbomFormat::NONE:
76
0
      break;
77
0
  }
78
0
}
79
80
bool cmExportSbomGenerator::AddPackageInformation(
81
  cmSpdxPackage& artifact, std::string const& name,
82
  cmPackageInformation const& package) const
83
0
{
84
0
  if (name.empty()) {
85
0
    return false;
86
0
  }
87
88
0
  cmSpdxOrganization org;
89
0
  org.SpdxId = cmStrCat("urn:", name, "#Organization");
90
0
  org.Name = name;
91
0
  org.CreationInfo = artifact.CreationInfo;
92
0
  artifact.OriginatedBy.emplace_back(std::move(org));
93
94
0
  if (package.Description) {
95
0
    artifact.Description = *package.Description;
96
0
  }
97
98
0
  if (package.Version) {
99
0
    artifact.PackageVersion = *package.Version;
100
0
  }
101
102
0
  if (package.PackageUrl) {
103
0
    artifact.PackageUrl = *package.PackageUrl;
104
0
  }
105
106
0
  if (package.License) {
107
0
    artifact.CopyrightText = *package.License;
108
0
  }
109
110
0
  artifact.BuiltTime = cmSystemTools::GetCurrentDateTime("%FT%TZ");
111
0
  cmSpdxExternalRef externalRef;
112
0
  externalRef.Locator = cmStrCat("cmake:find_package(", name, ")");
113
0
  externalRef.ExternalRefType = "buildSystem";
114
0
  return true;
115
0
}
116
117
cmSpdxCreationInfo cmExportSbomGenerator::GenerateCreationInfo() const
118
0
{
119
0
  cmSpdxCreationInfo ci;
120
0
  ci.SpdxId = "_:Build#CreationInfo";
121
0
  ci.Created = cmSystemTools::GetCurrentDateTime("%FT%TZ");
122
0
  ci.CreatedBy = { "https://gitlab.kitware.com/cmake/cmake" };
123
0
  ci.Comment = "This SBOM was generated from the CMakeLists.txt File";
124
0
  ci.SpecVersion = "3.0.1";
125
0
  return ci;
126
0
}
127
128
cmSpdxDocument cmExportSbomGenerator::GenerateSbom(
129
  cmSpdxCreationInfo const* ci) const
130
0
{
131
0
  cmSpdxDocument proj;
132
0
  proj.Name = PackageName;
133
0
  proj.SpdxId = cmStrCat("urn:", PackageName, "#SPDXDocument");
134
0
  proj.ProfileConformance = { "core", "software" };
135
0
  proj.CreationInfo = ci;
136
137
0
  if (!this->PackageDescription.empty()) {
138
0
    proj.Description = this->PackageDescription;
139
0
  }
140
141
0
  if (!this->PackageLicense.empty()) {
142
0
    proj.DataLicense = this->PackageLicense;
143
0
  }
144
145
0
  return proj;
146
0
}
147
148
cmSpdxPackage cmExportSbomGenerator::GenerateImportTarget(
149
  cmSpdxCreationInfo const* ci, cmGeneratorTarget const* target) const
150
0
{
151
0
  cmSpdxPackage package;
152
0
  package.SpdxId = cmStrCat("urn:", target->GetName(), "#Package");
153
0
  package.Name = target->GetName();
154
0
  package.PrimaryPurpose = GetPurpose(target->GetType());
155
0
  package.CreationInfo = ci;
156
157
0
  if (!this->PackageVersion.empty()) {
158
0
    package.PackageVersion = this->PackageVersion;
159
0
  }
160
161
0
  if (!this->PackageWebsite.empty()) {
162
0
    package.Homepage = this->PackageWebsite;
163
0
  }
164
165
0
  if (!this->PackageUrl.empty()) {
166
0
    package.DownloadLocation = this->PackageUrl;
167
0
  }
168
169
0
  return package;
170
0
}
171
172
void cmExportSbomGenerator::GenerateLinkProperties(
173
  cmSbomDocument& doc, cmSpdxDocument* project, cmSpdxCreationInfo const* ci,
174
  std::string const& libraries, TargetProperties const& current,
175
  std::vector<TargetProperties> const& allTargets) const
176
0
{
177
0
  auto itProp = current.Properties.find(libraries);
178
0
  if (itProp == current.Properties.end()) {
179
0
    return;
180
0
  }
181
182
0
  std::map<std::string, std::vector<std::string>> allowList = { { "LINK_ONLY",
183
0
                                                                  {} } };
184
0
  std::string interfaceLinkLibraries;
185
0
  if (!cmGeneratorExpression::ForbidGeneratorExpressions(
186
0
        current.Target, itProp->first, itProp->second, interfaceLinkLibraries,
187
0
        allowList)) {
188
0
    return;
189
0
  }
190
191
0
  auto makeRel = [&](char const* id, char const* desc) {
192
0
    cmSpdxRelationship r;
193
0
    r.SpdxId = cmStrCat("urn:", id, "#Relationship");
194
0
    r.RelationshipType = cmSpdxRelationship::RelationshipTypeId::DEPENDS_ON;
195
0
    r.Description = desc;
196
0
    r.From = current.Package;
197
0
    r.CreationInfo = ci;
198
0
    return r;
199
0
  };
200
201
0
  auto linkLibraries = makeRel("Static", "Linked Libraries");
202
0
  auto linkRequires = makeRel("Dynamic", "Required Runtime Libraries");
203
0
  auto buildRequires = makeRel("Shared", "Required Build-Time Libraries");
204
205
0
  auto addArtifact =
206
0
    [&](std::string const& name) -> std::pair<bool, cmSpdxPackage const*> {
207
0
    auto it = this->LinkTargets.find(name);
208
0
    if (it != this->LinkTargets.end()) {
209
0
      LinkInfo const& linkInfo = it->second;
210
0
      if (linkInfo.Package.empty()) {
211
0
        for (auto const& t : allTargets) {
212
0
          if (t.Target->GetName() == linkInfo.Component) {
213
0
            return { true, t.Package };
214
0
          }
215
0
        }
216
0
      }
217
0
      std::string pkgName =
218
0
        cmStrCat(linkInfo.Package, ":", linkInfo.Component);
219
0
      cmSpdxPackage pkg;
220
0
      pkg.Name = pkgName;
221
0
      pkg.SpdxId = cmStrCat("urn:", pkgName, "#Package");
222
0
      pkg.CreationInfo = ci;
223
0
      if (!linkInfo.Package.empty()) {
224
0
        auto const& pkgIt = this->Requirements.find(linkInfo.Package);
225
0
        if (pkgIt != this->Requirements.end() &&
226
0
            pkgIt->second.Components.count(linkInfo.Component) > 0) {
227
0
          this->AddPackageInformation(pkg, pkgIt->first, pkgIt->second);
228
0
        }
229
0
      }
230
0
      return { true, insert_back(project->Elements, std::move(pkg)) };
231
0
    }
232
233
0
    cmSpdxPackage pkg;
234
0
    pkg.SpdxId = cmStrCat("urn:", name, "#Package");
235
0
    pkg.Name = name;
236
0
    pkg.CreationInfo = ci;
237
0
    return { false, insert_back(project->Elements, std::move(pkg)) };
238
0
  };
239
240
0
  auto handleDependencies = [&](std::vector<std::string> const& names,
241
0
                                cmSpdxRelationship& internalDeps,
242
0
                                cmSpdxRelationship& externalDeps) {
243
0
    for (auto const& n : names) {
244
0
      auto res = addArtifact(n);
245
0
      if (!res.second) {
246
0
        continue;
247
0
      }
248
249
0
      if (res.first) {
250
0
        internalDeps.To.push_back(res.second);
251
0
      } else {
252
0
        externalDeps.To.push_back(res.second);
253
0
      }
254
0
    }
255
0
  };
256
257
0
  handleDependencies(allowList["LINK_ONLY"], linkLibraries, linkRequires);
258
0
  handleDependencies(cmList{ interfaceLinkLibraries }, linkLibraries,
259
0
                     buildRequires);
260
261
0
  if (!linkLibraries.To.empty()) {
262
0
    insert_back(doc.Graph, std::move(linkLibraries));
263
0
  }
264
0
  if (!linkRequires.To.empty()) {
265
0
    insert_back(doc.Graph, std::move(linkRequires));
266
0
  }
267
0
  if (!buildRequires.To.empty()) {
268
0
    insert_back(doc.Graph, std::move(buildRequires));
269
0
  }
270
0
}
271
272
bool cmExportSbomGenerator::GenerateProperties(
273
  cmSbomDocument& doc, cmSpdxDocument* proj, cmSpdxCreationInfo const* ci,
274
  TargetProperties const& current,
275
  std::vector<TargetProperties> const& allTargets) const
276
0
{
277
0
  this->GenerateLinkProperties(doc, proj, ci, "LINK_LIBRARIES", current,
278
0
                               allTargets);
279
0
  this->GenerateLinkProperties(doc, proj, ci, "INTERFACE_LINK_LIBRARIES",
280
0
                               current, allTargets);
281
0
  return true;
282
0
}
283
284
bool cmExportSbomGenerator::PopulateLinkLibrariesProperty(
285
  cmGeneratorTarget const* target,
286
  cmGeneratorExpression::PreprocessContext preprocessRule,
287
  ImportPropertyMap& properties)
288
0
{
289
0
  static std::array<std::string, 3> const linkIfaceProps = {
290
0
    { "LINK_LIBRARIES", "LINK_LIBRARIES_DIRECT",
291
0
      "LINK_LIBRARIES_DIRECT_EXCLUDE" }
292
0
  };
293
0
  bool hadLINK_LIBRARIES = false;
294
0
  for (std::string const& linkIfaceProp : linkIfaceProps) {
295
0
    if (cmValue input = target->GetProperty(linkIfaceProp)) {
296
0
      std::string prepro =
297
0
        cmGeneratorExpression::Preprocess(*input, preprocessRule);
298
0
      if (!prepro.empty()) {
299
0
        this->ResolveTargetsInGeneratorExpressions(prepro, target,
300
0
                                                   ReplaceFreeTargets);
301
0
        properties[linkIfaceProp] = prepro;
302
0
        hadLINK_LIBRARIES = true;
303
0
      }
304
0
    }
305
0
  }
306
0
  return hadLINK_LIBRARIES;
307
0
}
308
309
bool cmExportSbomGenerator::NoteLinkedTarget(
310
  cmGeneratorTarget const* target, std::string const& linkedName,
311
  cmGeneratorTarget const* linkedTarget)
312
0
{
313
0
  if (cm::contains(this->ExportedTargets, linkedTarget)) {
314
0
    this->LinkTargets.emplace(linkedName,
315
0
                              LinkInfo{ "", linkedTarget->GetExportName() });
316
0
    return true;
317
0
  }
318
319
0
  if (linkedTarget->IsImported()) {
320
0
    using Package = cm::optional<std::pair<std::string, cmPackageInformation>>;
321
0
    auto pkgInfo = [](cmTarget* t) -> Package {
322
0
      cmFindPackageStack pkgStack = t->GetFindPackageStack();
323
0
      if (!pkgStack.Empty()) {
324
0
        return std::make_pair(pkgStack.Top().Name,
325
0
                              *pkgStack.Top().PackageInfo);
326
0
      }
327
0
      std::string const pkgName =
328
0
        t->GetSafeProperty("EXPORT_FIND_PACKAGE_NAME");
329
0
      if (pkgName.empty()) {
330
0
        return cm::nullopt;
331
0
      }
332
0
      cmPackageInformation package;
333
0
      return std::make_pair(pkgName, package);
334
0
    }(linkedTarget->Target);
335
336
0
    if (!pkgInfo) {
337
0
      target->Makefile->IssueDiagnostic(
338
0
        cmDiagnostics::CMD_AUTHOR,
339
0
        cmStrCat("Target \"", target->GetName(),
340
0
                 "\" references imported target \"", linkedName,
341
0
                 "\" which does not come from any known package."));
342
0
      return false;
343
0
    }
344
345
0
    std::string const& pkgName = pkgInfo->first;
346
0
    auto const& prefix = cmStrCat(pkgName, "::");
347
0
    std::string component;
348
0
    if (!cmHasPrefix(linkedName, prefix)) {
349
0
      component = linkedName;
350
0
    } else {
351
0
      component = linkedName.substr(prefix.length());
352
0
    }
353
0
    this->LinkTargets.emplace(linkedName, LinkInfo{ pkgName, component });
354
0
    cmPackageInformation& req =
355
0
      this->Requirements.insert(std::move(*pkgInfo)).first->second;
356
0
    req.Components.emplace(std::move(component));
357
0
    return true;
358
0
  }
359
360
  // Target belongs to another export from this build.
361
0
  auto const& exportInfo = this->FindExportInfo(linkedTarget);
362
0
  if (exportInfo.Namespaces.size() == 1 && exportInfo.Sets.size() == 1) {
363
0
    auto const& linkNamespace = *exportInfo.Namespaces.begin();
364
0
    if (!cmHasSuffix(linkNamespace, "::")) {
365
0
      target->Makefile->IssueDiagnostic(
366
0
        cmDiagnostics::CMD_AUTHOR,
367
0
        cmStrCat("Target \"", target->GetName(), "\" references target \"",
368
0
                 linkedName,
369
0
                 "\", which does not use the standard namespace separator. "
370
0
                 "This is not allowed."));
371
0
    }
372
373
0
    std::string pkgName{ linkNamespace.data(), linkNamespace.size() - 2 };
374
0
    std::string component = linkedTarget->GetExportName();
375
0
    if (pkgName == this->GetPackageName()) {
376
0
      this->LinkTargets.emplace(linkedName, LinkInfo{ "", component });
377
0
    } else {
378
0
      this->LinkTargets.emplace(linkedName, LinkInfo{ pkgName, component });
379
0
      this->Requirements[pkgName].Components.emplace(std::move(component));
380
0
    }
381
0
    return true;
382
0
  }
383
384
  // Target belongs to multiple namespaces or multiple export sets.
385
  // cmExportFileGenerator::HandleMissingTarget should have complained about
386
  // this already.
387
0
  return false;
388
0
}