Coverage Report

Created: 2026-07-14 07:09

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/CMake/Source/cmSbomBuilder.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 "cmSbomBuilder.h"
4
5
#include <algorithm>
6
#include <array>
7
#include <functional>
8
#include <map>
9
#include <memory>
10
#include <set>
11
#include <sstream>
12
#include <string>
13
#include <utility>
14
#include <vector>
15
16
#include <cm/optional>
17
#include <cmext/algorithm>
18
#include <cmext/string_view>
19
20
#include "cmArgumentParserTypes.h"
21
#include "cmDiagnostics.h"
22
#include "cmExportFileGenerator.h"
23
#include "cmExportSet.h"
24
#include "cmFindPackageStack.h"
25
#include "cmGeneratorExpression.h"
26
#include "cmGeneratorTarget.h"
27
#include "cmList.h"
28
#include "cmLocalGenerator.h"
29
#include "cmMakefile.h"
30
#include "cmMessageType.h"
31
#include "cmSbomArguments.h"
32
#include "cmSbomObject.h"
33
#include "cmSpdx.h"
34
#include "cmSpdxSerializer.h"
35
#include "cmStringAlgorithms.h"
36
#include "cmSystemTools.h"
37
#include "cmTarget.h"
38
#include "cmTargetExport.h"
39
#include "cmTargetTypes.h"
40
#include "cmValue.h"
41
42
cmSpdxPackage::PurposeId GetPurpose(cm::TargetType type)
43
0
{
44
0
  switch (type) {
45
0
    case cm::TargetType::EXECUTABLE:
46
0
      return cmSpdxPackage::PurposeId::APPLICATION;
47
0
    case cm::TargetType::STATIC_LIBRARY:
48
0
    case cm::TargetType::SHARED_LIBRARY:
49
0
    case cm::TargetType::MODULE_LIBRARY:
50
0
    case cm::TargetType::OBJECT_LIBRARY:
51
0
    case cm::TargetType::INTERFACE_LIBRARY:
52
0
      return cmSpdxPackage::PurposeId::LIBRARY;
53
0
    case cm::TargetType::UTILITY:
54
0
      return cmSpdxPackage::PurposeId::SOURCE;
55
0
    case cm::TargetType::GLOBAL_TARGET:
56
0
    case cm::TargetType::UNKNOWN_LIBRARY:
57
0
    default:
58
0
      return cmSpdxPackage::PurposeId::ARCHIVE;
59
0
  }
60
0
}
61
62
cmSbomBuilder::cmSbomBuilder(cmSbomArguments args,
63
                             std::vector<cmExportSet*> exportSets,
64
                             cmLocalGenerator* lg)
65
0
  : LocalGenerator(lg)
66
0
  , ExportSets(std::move(exportSets))
67
0
  , PackageName(std::move(args.PackageName))
68
0
  , Namespace(cmStrCat(this->PackageName, "::"_s))
69
0
  , PackageVersion(std::move(args.Version))
70
0
  , PackageDescription(std::move(args.Description))
71
0
  , PackageWebsite(std::move(args.Website))
72
0
  , PackageUrl(std::move(args.PackageUrl))
73
0
  , DataLicense(std::move(args.DataLicense))
74
0
  , DefaultLicense(std::move(args.DefaultLicense))
75
0
  , PackageFormat(args.GetFormat())
76
0
{
77
0
}
78
79
std::set<cmGeneratorTarget const*> cmSbomBuilder::CollectTargets() const
80
0
{
81
0
  std::set<cmGeneratorTarget const*> targets;
82
0
  for (cmExportSet* exportSet : this->ExportSets) {
83
0
    for (auto const& te : exportSet->GetTargetExports()) {
84
0
      if (cmGeneratorTarget const* gt =
85
0
            this->LocalGenerator->FindGeneratorTargetToUse(te->TargetName)) {
86
0
        targets.emplace(gt);
87
0
      }
88
0
    }
89
0
  }
90
0
  return targets;
91
0
}
92
93
void cmSbomBuilder::Compute(cmLocalGenerator* lg)
94
0
{
95
0
  this->LocalGenerator = lg;
96
0
  if (!lg) {
97
0
    return;
98
0
  }
99
0
  for (cmExportSet* es : this->ExportSets) {
100
0
    es->Compute(lg);
101
0
  }
102
  // Populate the cache now (rather than at Generate time) so peer SBOMs can
103
  // query CoversTarget() during their own NoteLinkedTarget walks.
104
0
  this->SbomTargets = this->CollectTargets();
105
0
}
106
107
bool cmSbomBuilder::CoversTarget(cmGeneratorTarget const* target) const
108
0
{
109
0
  return cm::contains(this->SbomTargets, target);
110
0
}
111
112
bool cmSbomBuilder::CoversExportSet(cmExportSet const* set) const
113
0
{
114
0
  return std::find(this->ExportSets.cbegin(), this->ExportSets.cend(), set) !=
115
0
    this->ExportSets.cend();
116
0
}
117
118
bool cmSbomBuilder::GenerateForTargets(
119
  std::ostream& os, std::string const& config,
120
  cmGeneratorExpression::PreprocessContext preprocessContext)
121
0
{
122
0
  cmSbomDocument doc;
123
0
  doc.Graph.reserve(512);
124
125
0
  cmSpdxCreationInfo const* ci =
126
0
    insert_back(doc.Graph, this->GenerateCreationInfo());
127
0
  cmSpdxDocument* project = insert_back(doc.Graph, this->GenerateSbom(ci));
128
0
  std::vector<TargetProperties> targetProps;
129
0
  targetProps.reserve(this->SbomTargets.size());
130
131
0
  for (cmGeneratorTarget const* target : this->SbomTargets) {
132
0
    ImportPropertyMap properties;
133
0
    this->PopulateLinkLibrariesProperty(target, preprocessContext, properties);
134
0
    this->PopulateInterfaceLinkLibrariesProperty(target, preprocessContext,
135
0
                                                 properties);
136
0
    targetProps.push_back(TargetProperties{
137
0
      insert_back(project->RootElements,
138
0
                  this->GenerateImportTarget(ci, target)),
139
0
      target,
140
0
      std::move(properties),
141
0
    });
142
0
  }
143
144
0
  bool status = true;
145
0
  for (TargetProperties const& target : targetProps) {
146
0
    status &=
147
0
      this->GenerateProperties(doc, project, ci, target, targetProps, config);
148
0
  }
149
0
  if (status) {
150
0
    this->WriteSbom(doc, os);
151
0
  }
152
0
  return status;
153
0
}
154
155
void cmSbomBuilder::WriteSbom(cmSbomDocument& doc, std::ostream& os) const
156
0
{
157
0
  switch (this->PackageFormat) {
158
0
    case cmSbomArguments::SbomFormat::SPDX_3_0_JSON:
159
0
      cmSpdxSerializer{}.WriteSbom(os, cmSbomObject(doc));
160
0
      break;
161
0
    case cmSbomArguments::SbomFormat::NONE:
162
0
      break;
163
0
  }
164
0
}
165
166
bool cmSbomBuilder::AddPackageInformation(
167
  cmSpdxPackage& artifact, std::string const& name,
168
  cmPackageInformation const& package) const
169
0
{
170
0
  if (name.empty()) {
171
0
    return false;
172
0
  }
173
174
0
  cmSpdxOrganization org;
175
0
  org.SpdxId = cmStrCat("urn:", name, "#Organization");
176
0
  org.Name = name;
177
0
  org.CreationInfo = artifact.CreationInfo;
178
0
  artifact.OriginatedBy.emplace_back(std::move(org));
179
180
0
  if (package.Description) {
181
0
    artifact.Description = *package.Description;
182
0
  }
183
184
0
  if (package.Version) {
185
0
    artifact.PackageVersion = *package.Version;
186
0
  }
187
188
0
  if (package.PackageUrl) {
189
0
    artifact.PackageUrl = *package.PackageUrl;
190
0
  }
191
192
0
  if (package.License) {
193
0
    artifact.CopyrightText = *package.License;
194
0
  }
195
196
0
  artifact.BuiltTime = cmSystemTools::GetCurrentDateTime("%FT%TZ");
197
0
  cmSpdxExternalRef externalRef;
198
0
  externalRef.Locator = cmStrCat("cmake:find_package(", name, ")");
199
0
  externalRef.ExternalRefType = "buildSystem";
200
0
  return true;
201
0
}
202
203
cmSpdxCreationInfo cmSbomBuilder::GenerateCreationInfo() const
204
0
{
205
0
  cmSpdxCreationInfo ci;
206
0
  ci.SpdxId = "_:Build#CreationInfo";
207
0
  ci.Created = cmSystemTools::GetCurrentDateTime("%FT%TZ");
208
0
  ci.CreatedBy = { "https://gitlab.kitware.com/cmake/cmake" };
209
0
  ci.Comment = "This SBOM was generated from the CMakeLists.txt File";
210
0
  ci.SpecVersion = "3.0.1";
211
0
  return ci;
212
0
}
213
214
cmSpdxDocument cmSbomBuilder::GenerateSbom(cmSpdxCreationInfo const* ci) const
215
0
{
216
0
  cmSpdxDocument proj;
217
0
  proj.Name = PackageName;
218
0
  proj.SpdxId = cmStrCat("urn:", PackageName, "#SPDXDocument");
219
0
  proj.ProfileConformance = { "core", "software" };
220
0
  proj.CreationInfo = ci;
221
222
0
  if (!this->PackageDescription.empty()) {
223
0
    proj.Description = this->PackageDescription;
224
0
  }
225
226
0
  if (!this->DataLicense.empty()) {
227
0
    cmSpdxLicenseExpression license;
228
0
    license.SpdxId = cmStrCat("urn:", PackageName, "#LicenseExpression");
229
0
    license.CreationInfo = ci;
230
0
    license.LicenseExpression = this->DataLicense;
231
0
    proj.DataLicense = license;
232
0
  }
233
234
0
  return proj;
235
0
}
236
237
cmSpdxPackage cmSbomBuilder::GenerateImportTarget(
238
  cmSpdxCreationInfo const* ci, cmGeneratorTarget const* target) const
239
0
{
240
0
  cmSpdxPackage package;
241
0
  package.SpdxId = cmStrCat("urn:", target->GetName(), "#Package");
242
0
  package.Name = target->GetName();
243
0
  package.PrimaryPurpose = GetPurpose(target->GetType());
244
0
  package.CreationInfo = ci;
245
246
0
  if (!this->PackageVersion.empty()) {
247
0
    package.PackageVersion = this->PackageVersion;
248
0
  }
249
250
0
  if (!this->PackageWebsite.empty()) {
251
0
    package.Homepage = this->PackageWebsite;
252
0
  }
253
254
0
  if (!this->PackageUrl.empty()) {
255
0
    package.DownloadLocation = this->PackageUrl;
256
0
  }
257
258
0
  return package;
259
0
}
260
261
bool cmSbomBuilder::GenerateLinkProperties(
262
  cmSbomDocument& doc, cmSpdxDocument* project, cmSpdxCreationInfo const* ci,
263
  std::string const& libraries, TargetProperties const& current,
264
  std::vector<TargetProperties> const& allTargets,
265
  std::string const& config) const
266
0
{
267
0
  auto itProp = current.Properties.find(libraries);
268
0
  if (itProp == current.Properties.end()) {
269
0
    return true;
270
0
  }
271
272
0
  cmGeneratorExpression ge(*current.Target->Makefile->GetCMakeInstance());
273
0
  std::unique_ptr<cmCompiledGeneratorExpression> cge =
274
0
    ge.Parse(itProp->second);
275
0
  std::string evaluatedLibraries =
276
0
    cge->Evaluate(current.Target->GetLocalGenerator(), config, current.Target);
277
278
0
  if (cge->GetHadHeadSensitiveCondition()) {
279
0
    current.Target->Makefile->IssueMessage(
280
0
      MessageType::FATAL_ERROR,
281
0
      cmStrCat("Property \"", libraries, "\" of target \"",
282
0
               current.Target->GetName(),
283
0
               "\" contains a generator expression that is not allowed for "
284
0
               "SBOM generation."));
285
0
    return false;
286
0
  }
287
288
0
  auto makeRel = [&](char const* id, char const* desc) {
289
0
    cmSpdxRelationship r;
290
0
    r.SpdxId = cmStrCat("urn:", id, "#Relationship");
291
0
    r.RelationshipType = cmSpdxRelationship::RelationshipTypeId::DEPENDS_ON;
292
0
    r.Description = desc;
293
0
    r.From = current.Package;
294
0
    r.CreationInfo = ci;
295
0
    return r;
296
0
  };
297
298
0
  auto linkLibraries = makeRel("Static", "Linked Libraries");
299
0
  auto linkRequires = makeRel("Dynamic", "Required Runtime Libraries");
300
0
  auto buildRequires = makeRel("Shared", "Required Build-Time Libraries");
301
302
0
  auto addArtifact =
303
0
    [&](std::string const& name) -> std::pair<bool, cmSpdxPackage const*> {
304
0
    auto it = this->LinkTargets.find(name);
305
0
    if (it != this->LinkTargets.end()) {
306
0
      LinkInfo const& linkInfo = it->second;
307
0
      if (linkInfo.Package.empty()) {
308
0
        for (auto const& t : allTargets) {
309
0
          if (t.Target->GetName() == linkInfo.Component) {
310
0
            return { true, t.Package };
311
0
          }
312
0
        }
313
0
      }
314
0
      std::string pkgName =
315
0
        cmStrCat(linkInfo.Package, ":", linkInfo.Component);
316
0
      cmSpdxPackage pkg;
317
0
      pkg.Name = pkgName;
318
0
      pkg.SpdxId = cmStrCat("urn:", pkgName, "#Package");
319
0
      pkg.CreationInfo = ci;
320
0
      if (!linkInfo.Package.empty()) {
321
0
        auto const& pkgIt = this->Requirements.find(linkInfo.Package);
322
0
        if (pkgIt != this->Requirements.end() &&
323
0
            pkgIt->second.Components.count(linkInfo.Component) > 0) {
324
0
          this->AddPackageInformation(pkg, pkgIt->first, pkgIt->second);
325
0
        }
326
0
      }
327
328
0
      cmSpdxPackage const* pkgPtr =
329
0
        insert_back(project->Elements, std::move(pkg));
330
0
      if (!linkInfo.License.empty() &&
331
0
          !cm::contains(this->GeneratedLinkLicenses, name)) {
332
0
        this->GeneratedLinkLicenses.emplace(name);
333
334
0
        cmSpdxLicenseExpression license;
335
0
        license.SpdxId = cmStrCat("urn:", name, "#LicenseExpression");
336
0
        license.CreationInfo = ci;
337
0
        license.LicenseExpression = linkInfo.License;
338
339
0
        cmSpdxRelationship relHasLicense;
340
0
        relHasLicense.SpdxId =
341
0
          cmStrCat("urn:", name, "#DeclaredLicenseRelationship");
342
0
        relHasLicense.CreationInfo = ci;
343
0
        relHasLicense.RelationshipType =
344
0
          cmSpdxRelationship::HAS_DECLARED_LICENSE;
345
0
        relHasLicense.From = pkgPtr;
346
0
        relHasLicense.To.emplace_back(std::move(license));
347
348
0
        insert_back(doc.Graph, std::move(relHasLicense));
349
0
      }
350
351
0
      return { true, pkgPtr };
352
0
    }
353
354
0
    cmSpdxPackage pkg;
355
0
    pkg.SpdxId = cmStrCat("urn:", name, "#Package");
356
0
    pkg.Name = name;
357
0
    pkg.CreationInfo = ci;
358
0
    return { false, insert_back(project->Elements, std::move(pkg)) };
359
0
  };
360
361
0
  cmList names{ evaluatedLibraries };
362
0
  names.sort();
363
0
  names.remove_duplicates();
364
0
  for (std::string const& n : names) {
365
0
    auto res = addArtifact(n);
366
0
    if (!res.second) {
367
0
      continue;
368
0
    }
369
370
0
    if (res.first) {
371
0
      linkLibraries.To.push_back(res.second);
372
0
    } else {
373
0
      buildRequires.To.push_back(res.second);
374
0
    }
375
0
  }
376
377
0
  if (!linkLibraries.To.empty()) {
378
0
    insert_back(doc.Graph, std::move(linkLibraries));
379
0
  }
380
0
  if (!linkRequires.To.empty()) {
381
0
    insert_back(doc.Graph, std::move(linkRequires));
382
0
  }
383
0
  if (!buildRequires.To.empty()) {
384
0
    insert_back(doc.Graph, std::move(buildRequires));
385
0
  }
386
0
  return true;
387
0
}
388
389
bool cmSbomBuilder::GenerateProperties(
390
  cmSbomDocument& doc, cmSpdxDocument* proj, cmSpdxCreationInfo const* ci,
391
  TargetProperties const& current,
392
  std::vector<TargetProperties> const& allTargets,
393
  std::string const& config) const
394
0
{
395
0
  bool status = true;
396
0
  status &= this->GenerateLinkProperties(doc, proj, ci, "LINK_LIBRARIES",
397
0
                                         current, allTargets, config);
398
0
  status &= this->GenerateLinkProperties(
399
0
    doc, proj, ci, "INTERFACE_LINK_LIBRARIES", current, allTargets, config);
400
0
  status &= this->GenerateMetaProperties(doc, proj, ci, current);
401
0
  return status;
402
0
}
403
404
bool cmSbomBuilder::GenerateMetaProperties(
405
  cmSbomDocument& doc, cmSpdxDocument* /*project*/,
406
  cmSpdxCreationInfo const* ci, TargetProperties const& current) const
407
0
{
408
0
  std::string licenseExpr = this->DefaultLicense;
409
0
  cmValue licenseExprProp = current.Target->GetProperty("SPDX_LICENSE");
410
0
  if (licenseExprProp) {
411
0
    licenseExpr = licenseExprProp;
412
0
  }
413
0
  if (!licenseExpr.empty()) {
414
0
    auto const& tgtName = current.Target->GetName();
415
416
0
    cmSpdxLicenseExpression license;
417
0
    license.SpdxId = cmStrCat("urn:", tgtName, "#LicenseExpression");
418
0
    license.CreationInfo = ci;
419
0
    license.LicenseExpression = std::move(licenseExpr);
420
421
0
    cmSpdxRelationship relHasLicense;
422
0
    relHasLicense.SpdxId =
423
0
      cmStrCat("urn:", tgtName, "#DeclaredLicenseRelationship");
424
0
    relHasLicense.CreationInfo = ci;
425
0
    relHasLicense.RelationshipType = cmSpdxRelationship::HAS_DECLARED_LICENSE;
426
0
    relHasLicense.From = current.Package;
427
0
    relHasLicense.To.emplace_back(std::move(license));
428
429
0
    insert_back(doc.Graph, std::move(relHasLicense));
430
0
  }
431
0
  return true;
432
0
}
433
434
bool cmSbomBuilder::PopulateLinkLibrariesProperty(
435
  cmGeneratorTarget const* target,
436
  cmGeneratorExpression::PreprocessContext preprocessRule,
437
  ImportPropertyMap& properties)
438
0
{
439
0
  static std::array<std::string, 3> const linkIfaceProps = { {
440
0
    "LINK_LIBRARIES",
441
0
    "LINK_LIBRARIES_DIRECT",
442
0
    "LINK_LIBRARIES_DIRECT_EXCLUDE",
443
0
  } };
444
0
  bool hadLINK_LIBRARIES = false;
445
0
  for (std::string const& linkIfaceProp : linkIfaceProps) {
446
0
    if (cmValue input = target->GetProperty(linkIfaceProp)) {
447
0
      std::string prepro =
448
0
        cmGeneratorExpression::Preprocess(*input, preprocessRule);
449
0
      if (!prepro.empty()) {
450
0
        this->ResolveTargetsInGeneratorExpressions(prepro, target);
451
0
        properties[linkIfaceProp] = prepro;
452
0
        hadLINK_LIBRARIES = true;
453
0
      }
454
0
    }
455
0
  }
456
0
  return hadLINK_LIBRARIES;
457
0
}
458
459
bool cmSbomBuilder::AddTargetNamespace(std::string& input,
460
                                       cmGeneratorTarget const* target,
461
                                       cmLocalGenerator const* lg)
462
0
{
463
0
  cmGeneratorTarget::TargetOrString resolved =
464
0
    target->ResolveTargetReference(input, lg);
465
466
0
  cmGeneratorTarget* tgt = resolved.Target;
467
0
  if (!tgt) {
468
0
    input = resolved.String;
469
0
    return false;
470
0
  }
471
472
0
  if (tgt->IsImported()) {
473
0
    input = tgt->GetName();
474
0
    return this->NoteLinkedTarget(target, input, tgt);
475
0
  }
476
477
0
  if (this->SbomTargets.find(tgt) != this->SbomTargets.end()) {
478
0
    input = this->Namespace + tgt->GetExportName();
479
0
  } else {
480
0
    input = tgt->GetName();
481
0
  }
482
483
0
  return this->NoteLinkedTarget(target, input, tgt);
484
0
}
485
486
bool cmSbomBuilder::NoteLinkedTarget(cmGeneratorTarget const* target,
487
                                     std::string const& linkedName,
488
                                     cmGeneratorTarget const* linkedTarget)
489
0
{
490
0
  auto linkedLicense = linkedTarget->GetSafeProperty("SPDX_LICENSE");
491
492
0
  if (cm::contains(this->SbomTargets, linkedTarget)) {
493
0
    this->LinkTargets.emplace(
494
0
      linkedName,
495
0
      LinkInfo{ "", linkedTarget->GetExportName(), linkedLicense });
496
0
    return true;
497
0
  }
498
499
0
  if (linkedTarget->IsImported()) {
500
0
    using Package = cm::optional<std::pair<std::string, cmPackageInformation>>;
501
0
    auto pkgInfo = [](cmTarget* t) -> Package {
502
0
      cmFindPackageStack pkgStack = t->GetFindPackageStack();
503
0
      if (!pkgStack.Empty()) {
504
0
        return std::make_pair(pkgStack.Top().Name,
505
0
                              *pkgStack.Top().PackageInfo);
506
0
      }
507
0
      std::string const pkgName =
508
0
        t->GetSafeProperty("EXPORT_FIND_PACKAGE_NAME");
509
0
      if (pkgName.empty()) {
510
0
        return cm::nullopt;
511
0
      }
512
0
      cmPackageInformation package;
513
0
      return std::make_pair(pkgName, package);
514
0
    }(linkedTarget->Target);
515
516
0
    if (!pkgInfo) {
517
0
      target->Makefile->IssueDiagnostic(
518
0
        cmDiagnostics::CMD_AUTHOR,
519
0
        cmStrCat("Target \"", target->GetName(),
520
0
                 "\" references imported target \"", linkedName,
521
0
                 "\" which does not come from any known package."));
522
0
      return false;
523
0
    }
524
525
0
    std::string const& pkgName = pkgInfo->first;
526
0
    auto const& prefix = cmStrCat(pkgName, "::");
527
0
    std::string component;
528
0
    if (!cmHasPrefix(linkedName, prefix)) {
529
0
      component = linkedName;
530
0
    } else {
531
0
      component = linkedName.substr(prefix.length());
532
0
    }
533
0
    this->LinkTargets.emplace(linkedName,
534
0
                              LinkInfo{ pkgName, component, linkedLicense });
535
0
    cmPackageInformation& req =
536
0
      this->Requirements.insert(std::move(*pkgInfo)).first->second;
537
0
    req.Components.emplace(std::move(component));
538
0
    return true;
539
0
  }
540
541
  // Target belongs to another export from this build or install.
542
  // The leaf class chooses which export map to consult.
543
0
  auto const& exportInfo = this->FindExportInfoFor(linkedTarget);
544
0
  if (exportInfo.Namespaces.size() == 1 && exportInfo.Sets.size() == 1) {
545
0
    auto const& linkNamespace = *exportInfo.Namespaces.begin();
546
0
    if (!cmHasSuffix(linkNamespace, "::")) {
547
0
      target->Makefile->IssueDiagnostic(
548
0
        cmDiagnostics::CMD_AUTHOR,
549
0
        cmStrCat("Target \"", target->GetName(), "\" references target \"",
550
0
                 linkedName,
551
0
                 "\", whose export does not use the standard namespace "
552
0
                 "separator.  The dependency will be recorded by its bare "
553
0
                 "target name without provenance."));
554
0
      return false;
555
0
    }
556
557
0
    std::string pkgName{ linkNamespace.data(), linkNamespace.size() - 2 };
558
0
    std::string component = linkedTarget->GetExportName();
559
0
    if (pkgName == this->GetPackageName()) {
560
0
      this->LinkTargets.emplace(linkedName,
561
0
                                LinkInfo{ "", component, linkedLicense });
562
0
    } else {
563
0
      this->LinkTargets.emplace(linkedName,
564
0
                                LinkInfo{ pkgName, component, linkedLicense });
565
0
      this->Requirements[pkgName].Components.emplace(std::move(component));
566
0
    }
567
0
    return true;
568
0
  }
569
570
0
  if (exportInfo.Sets.empty()) {
571
    // install(export) provenance is unavailable.  Fall back to SBOM-level
572
    // attribution: any peer SBOM (of the same build/install mode) that
573
    // covers this target lends its package name.  install(export) wins
574
    // when both are available; only reached here when install(export) is
575
    // absent.
576
0
    auto const& sbomInfo = this->FindSbomInfoFor(linkedTarget);
577
0
    if (sbomInfo.Packages.empty()) {
578
0
      target->Makefile->IssueMessage(
579
0
        MessageType::FATAL_ERROR,
580
0
        cmStrCat("Target \"", target->GetName(), "\" references target \"",
581
0
                 linkedName,
582
0
                 "\" which has no install(EXPORT)/export(EXPORT) namespace "
583
0
                 "and is not covered by any SBOM.  An SBOM cannot attribute "
584
0
                 "this dependency.  Give \"",
585
0
                 linkedTarget->GetName(),
586
0
                 "\" an install(EXPORT)/export(EXPORT) with a NAMESPACE, or "
587
0
                 "include it in an install(SBOM)/export(SBOM)."));
588
0
      return false;
589
0
    }
590
0
    std::string const& pkgName = sbomInfo.Packages.front();
591
0
    if (sbomInfo.Packages.size() > 1) {
592
0
      target->Makefile->IssueDiagnostic(
593
0
        cmDiagnostics::CMD_AUTHOR,
594
0
        cmStrCat(
595
0
          "Target \"", target->GetName(), "\" references target \"",
596
0
          linkedName,
597
0
          "\" which has no install(EXPORT)/export(EXPORT) namespace and is "
598
0
          "covered by multiple SBOMs: ",
599
0
          cmJoin(sbomInfo.Packages, ", "), ".  Attributing to \"", pkgName,
600
0
          "\" (first alphabetically)."));
601
0
    }
602
0
    std::string component = linkedTarget->GetExportName();
603
0
    this->LinkTargets.emplace(linkedName,
604
0
                              LinkInfo{ pkgName, component, linkedLicense });
605
0
    this->Requirements[pkgName].Components.emplace(std::move(component));
606
0
    return true;
607
0
  }
608
609
0
  std::ostringstream e;
610
0
  e << "Target \"" << target->GetName() << "\" references target \""
611
0
    << linkedName << "\" ";
612
0
  if (exportInfo.Sets.size() == 1) {
613
0
    e << "that is in an export set which is exported multiple times "
614
0
         "with different namespaces: ";
615
0
  } else {
616
0
    e << "that is in multiple export sets: ";
617
0
  }
618
0
  e << cmJoin(exportInfo.Files, ", ") << ".\n"
619
0
    << "An SBOM cannot attribute a dependency exported in more than one "
620
0
       "export set or with more than one namespace.  Consider "
621
0
       "consolidating the exports of the \""
622
0
    << linkedTarget->GetName() << "\" target to a single export.";
623
0
  target->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str());
624
0
  return false;
625
0
}
626
627
void cmSbomBuilder::ResolveTargetsInGeneratorExpression(
628
  std::string& input, cmGeneratorTarget const* target,
629
  cmLocalGenerator const* lg)
630
0
{
631
0
  auto err = cmResolveTargetsInGeneratorExpression(
632
0
    input, [this, target, lg](std::string& name) {
633
0
      return this->AddTargetNamespace(name, target, lg);
634
0
    });
635
0
  if (err) {
636
0
    target->GetLocalGenerator()->IssueMessage(MessageType::FATAL_ERROR, *err);
637
0
  }
638
0
}
639
640
void cmSbomBuilder::ResolveTargetsInGeneratorExpressions(
641
  std::string& input, cmGeneratorTarget const* target)
642
0
{
643
0
  cmLocalGenerator const* lg = target->GetLocalGenerator();
644
0
  std::vector<std::string> parts;
645
0
  cmGeneratorExpression::Split(input, parts);
646
647
0
  std::string sep;
648
0
  input.clear();
649
0
  for (std::string& li : parts) {
650
0
    if (target->IsLinkLookupScope(li, lg)) {
651
0
      continue;
652
0
    }
653
0
    if (cmGeneratorExpression::Find(li) == std::string::npos) {
654
0
      this->AddTargetNamespace(li, target, lg);
655
0
    } else {
656
0
      this->ResolveTargetsInGeneratorExpression(li, target, lg);
657
0
    }
658
0
    input += sep + li;
659
0
    sep = ";";
660
0
  }
661
0
}
662
663
bool cmSbomBuilder::PopulateInterfaceLinkLibrariesProperty(
664
  cmGeneratorTarget const* target,
665
  cmGeneratorExpression::PreprocessContext preprocessRule,
666
  ImportPropertyMap& properties)
667
0
{
668
0
  if (!target->IsLinkable()) {
669
0
    return false;
670
0
  }
671
0
  static std::array<std::string, 3> const linkIfaceProps = {
672
0
    { "INTERFACE_LINK_LIBRARIES", "INTERFACE_LINK_LIBRARIES_DIRECT",
673
0
      "INTERFACE_LINK_LIBRARIES_DIRECT_EXCLUDE" }
674
0
  };
675
0
  bool hadINTERFACE_LINK_LIBRARIES = false;
676
0
  for (std::string const& linkIfaceProp : linkIfaceProps) {
677
0
    if (cmValue input = target->GetProperty(linkIfaceProp)) {
678
0
      std::string prepro =
679
0
        cmGeneratorExpression::Preprocess(*input, preprocessRule);
680
0
      if (!prepro.empty()) {
681
0
        this->ResolveTargetsInGeneratorExpressions(prepro, target);
682
0
        properties[linkIfaceProp] = prepro;
683
0
        hadINTERFACE_LINK_LIBRARIES = true;
684
0
      }
685
0
    }
686
0
  }
687
0
  return hadINTERFACE_LINK_LIBRARIES;
688
0
}