Coverage Report

Created: 2026-03-12 06:35

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/CMake/Source/cmPackageInfoReader.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 "cmPackageInfoReader.h"
4
5
#include <algorithm>
6
#include <initializer_list>
7
#include <limits>
8
#include <unordered_map>
9
#include <utility>
10
11
#include <cmext/algorithm>
12
#include <cmext/string_view>
13
14
#include <cm3p/json/reader.h>
15
#include <cm3p/json/value.h>
16
#include <cm3p/json/version.h>
17
18
#include "cmsys/FStream.hxx"
19
#include "cmsys/RegularExpression.hxx"
20
21
#include "cmCxxModuleMetadata.h"
22
#include "cmExecutionStatus.h"
23
#include "cmList.h"
24
#include "cmListFileCache.h"
25
#include "cmMakefile.h"
26
#include "cmMessageType.h"
27
#include "cmStringAlgorithms.h"
28
#include "cmSystemTools.h"
29
#include "cmTarget.h"
30
#include "cmValue.h"
31
32
namespace {
33
34
// Map of CPS language names to CMake language name.  Case insensitivity is
35
// achieved by converting the CPS value to lower case, so keys in this map must
36
// be lower case.
37
std::unordered_map<std::string, std::string> Languages = {
38
  // clang-format off
39
  { "c", "C" },
40
  { "c++", "CXX" },
41
  { "cpp", "CXX" },
42
  { "cxx", "CXX" },
43
  { "objc", "OBJC" },
44
  { "objc++", "OBJCXX" },
45
  { "objcpp", "OBJCXX" },
46
  { "objcxx", "OBJCXX" },
47
  { "swift", "swift" },
48
  { "hip", "HIP" },
49
  { "cuda", "CUDA" },
50
  { "ispc", "ISPC" },
51
  { "c#", "CSharp" },
52
  { "csharp", "CSharp" },
53
  { "fortran", "Fortran" },
54
  // clang-format on
55
};
56
57
enum LanguageGlobOption
58
{
59
  DisallowGlob,
60
  AllowGlob,
61
};
62
63
cm::string_view MapLanguage(cm::string_view lang,
64
                            LanguageGlobOption glob = AllowGlob)
65
0
{
66
0
  if (glob == AllowGlob && lang == "*"_s) {
67
0
    return "*"_s;
68
0
  }
69
0
  auto const li = Languages.find(cmSystemTools::LowerCase(lang));
70
0
  if (li != Languages.end()) {
71
0
    return li->second;
72
0
  }
73
0
  return {};
74
0
}
75
76
std::string GetRealPath(std::string const& path)
77
0
{
78
0
  return cmSystemTools::GetRealPath(path);
79
0
}
80
81
std::string GetRealDir(std::string const& path)
82
0
{
83
0
  return cmSystemTools::GetFilenamePath(cmSystemTools::GetRealPath(path));
84
0
}
85
86
Json::Value ReadJson(std::string const& fileName)
87
0
{
88
  // Open the specified file.
89
0
  cmsys::ifstream file(fileName.c_str(), std::ios::in | std::ios::binary);
90
0
  if (!file) {
91
#if JSONCPP_VERSION_HEXA < 0x01070300
92
    return Json::Value::null;
93
#else
94
0
    return Json::Value::nullSingleton();
95
0
#endif
96
0
  }
97
98
  // Read file content and translate JSON.
99
0
  Json::Value data;
100
0
  Json::CharReaderBuilder builder;
101
0
  builder["collectComments"] = false;
102
0
  if (!Json::parseFromStream(builder, file, &data, nullptr)) {
103
#if JSONCPP_VERSION_HEXA < 0x01070300
104
    return Json::Value::null;
105
#else
106
0
    return Json::Value::nullSingleton();
107
0
#endif
108
0
  }
109
110
0
  return data;
111
0
}
112
113
std::string ToString(Json::Value const& value)
114
0
{
115
0
  if (value.isString()) {
116
0
    return value.asString();
117
0
  }
118
0
  return {};
119
0
}
120
121
bool CheckSchemaVersion(Json::Value const& data)
122
0
{
123
0
  std::string const& version = ToString(data["cps_version"]);
124
125
  // Check that a valid version is specified.
126
0
  if (version.empty()) {
127
0
    return false;
128
0
  }
129
130
  // Check that we understand this version.
131
0
  return cmSystemTools::VersionCompare(cmSystemTools::OP_GREATER_EQUAL,
132
0
                                       version, "0.13") &&
133
0
    cmSystemTools::VersionCompare(cmSystemTools::OP_LESS, version, "0.15");
134
135
  // TODO Eventually this probably needs to return the version tuple, and
136
  // should share code with cmPackageInfoReader::ParseVersion.
137
0
}
138
139
bool ComparePathSuffix(std::string const& path, std::string const& suffix)
140
0
{
141
0
  std::string::size_type const ps = path.size();
142
0
  std::string::size_type const ss = suffix.size();
143
144
0
  if (ss > ps) {
145
0
    return false;
146
0
  }
147
148
0
  return cmSystemTools::ComparePath(path.substr(ps - ss), suffix);
149
0
}
150
151
std::string DeterminePrefix(std::string const& filepath,
152
                            Json::Value const& data)
153
0
{
154
  // First check if an absolute prefix was supplied.
155
0
  std::string prefix = ToString(data["prefix"]);
156
0
  if (!prefix.empty()) {
157
    // Ensure that the specified prefix is valid.
158
0
    if (cmsys::SystemTools::FileIsFullPath(prefix) &&
159
0
        cmsys::SystemTools::FileIsDirectory(prefix)) {
160
0
      cmSystemTools::ConvertToUnixSlashes(prefix);
161
0
      return prefix;
162
0
    }
163
    // The specified absolute prefix is not valid.
164
0
    return {};
165
0
  }
166
167
  // Get and validate prefix-relative path.
168
0
  std::string const& absPath = cmSystemTools::GetFilenamePath(filepath);
169
0
  std::string relPath = ToString(data["cps_path"]);
170
0
  cmSystemTools::ConvertToUnixSlashes(relPath);
171
0
  if (relPath.empty() || !cmHasLiteralPrefix(relPath, "@prefix@")) {
172
    // The relative prefix is not valid.
173
0
    return {};
174
0
  }
175
0
  if (relPath.size() == 8) {
176
    // The relative path is exactly "@prefix@".
177
0
    return absPath;
178
0
  }
179
0
  if (relPath[8] != '/') {
180
    // The relative prefix is not valid.
181
0
    return {};
182
0
  }
183
0
  relPath = relPath.substr(8);
184
185
  // Get directory portion of the absolute path.
186
0
  if (ComparePathSuffix(absPath, relPath)) {
187
0
    return absPath.substr(0, absPath.size() - relPath.size());
188
0
  }
189
190
0
  for (auto* const f : { GetRealPath, GetRealDir }) {
191
0
    std::string const& tmpPath = (*f)(absPath);
192
0
    if (!cmSystemTools::ComparePath(tmpPath, absPath) &&
193
0
        ComparePathSuffix(tmpPath, relPath)) {
194
0
      return tmpPath.substr(0, tmpPath.size() - relPath.size());
195
0
    }
196
0
  }
197
198
0
  return {};
199
0
}
200
201
// Extract key name from value iterator as string_view.
202
cm::string_view IterKey(Json::Value::const_iterator iter)
203
0
{
204
0
  char const* end;
205
0
  char const* const start = iter.memberName(&end);
206
0
  return { start, static_cast<std::string::size_type>(end - start) };
207
0
}
208
209
// Get list-of-strings value from object.
210
std::vector<std::string> ReadList(Json::Value const& arr)
211
0
{
212
0
  std::vector<std::string> result;
213
214
0
  if (arr.isArray()) {
215
0
    for (Json::Value const& val : arr) {
216
0
      if (val.isString()) {
217
0
        result.push_back(val.asString());
218
0
      }
219
0
    }
220
0
  }
221
222
0
  return result;
223
0
}
224
225
std::vector<std::string> ReadList(Json::Value const& data, char const* key)
226
0
{
227
0
  return ReadList(data[key]);
228
0
}
229
230
std::string NormalizeTargetName(std::string const& name,
231
                                std::string const& context)
232
0
{
233
0
  if (cmHasPrefix(name, ':')) {
234
0
    return cmStrCat(context, ':', name);
235
0
  }
236
237
0
  std::string::size_type const n = name.find_first_of(':');
238
0
  if (n != std::string::npos) {
239
0
    cm::string_view v{ name };
240
0
    return cmStrCat(v.substr(0, n), ':', v.substr(n));
241
0
  }
242
0
  return name;
243
0
}
244
245
void AppendProperty(cmMakefile* makefile, cmTarget* target,
246
                    cm::string_view property, cm::string_view configuration,
247
                    std::string const& value)
248
0
{
249
0
  std::string const fullprop = cmStrCat("INTERFACE_", property);
250
0
  if (!configuration.empty()) {
251
0
    std::string const genexValue =
252
0
      cmStrCat("$<$<CONFIG:", configuration, ">:", value, '>');
253
0
    target->AppendProperty(fullprop, genexValue, makefile->GetBacktrace());
254
0
  } else {
255
0
    target->AppendProperty(fullprop, value, makefile->GetBacktrace());
256
0
  }
257
0
}
258
259
void AppendImportProperty(cmMakefile* makefile, cmTarget* target,
260
                          cm::string_view property,
261
                          cm::string_view configuration,
262
                          std::string const& value)
263
0
{
264
0
  if (!configuration.empty()) {
265
0
    std::string const fullprop = cmStrCat(
266
0
      "IMPORTED_", property, '_', cmSystemTools::UpperCase(configuration));
267
0
    target->AppendProperty(fullprop, value, makefile->GetBacktrace());
268
0
  } else {
269
0
    std::string const fullprop = cmStrCat("IMPORTED_", property);
270
0
    target->AppendProperty(fullprop, value, makefile->GetBacktrace());
271
0
  }
272
0
}
273
274
template <typename Transform>
275
void AppendLanguageProperties(cmMakefile* makefile, cmTarget* target,
276
                              cm::string_view property,
277
                              cm::string_view configuration,
278
                              Json::Value const& data, char const* key,
279
                              Transform transform)
280
0
{
281
0
  Json::Value const& value = data[key];
282
0
  if (value.isArray()) {
283
0
    for (std::string v : ReadList(value)) {
284
0
      AppendProperty(makefile, target, property, configuration,
285
0
                     transform(std::move(v)));
286
0
    }
287
0
  } else if (value.isObject()) {
288
0
    for (auto vi = value.begin(), ve = value.end(); vi != ve; ++vi) {
289
0
      cm::string_view const originalLang = IterKey(vi);
290
0
      cm::string_view const lang = MapLanguage(originalLang);
291
0
      if (lang.empty()) {
292
0
        makefile->IssueMessage(MessageType::WARNING,
293
0
                               cmStrCat(R"(ignoring unknown language ")"_s,
294
0
                                        originalLang, R"(" in )"_s, key,
295
0
                                        " for "_s, target->GetName()));
296
0
        continue;
297
0
      }
298
299
0
      if (lang == "*"_s) {
300
0
        for (std::string v : ReadList(*vi)) {
301
0
          AppendProperty(makefile, target, property, configuration,
302
0
                         transform(std::move(v)));
303
0
        }
304
0
      } else {
305
0
        for (std::string v : ReadList(*vi)) {
306
0
          v = cmStrCat("$<$<COMPILE_LANGUAGE:"_s, lang, ">:"_s,
307
0
                       transform(std::move(v)), '>');
308
0
          AppendProperty(makefile, target, property, configuration, v);
309
0
        }
310
0
      }
311
0
    }
312
0
  }
313
0
}
314
315
void AddCompileFeature(cmMakefile* makefile, cmTarget* target,
316
                       cm::string_view configuration, std::string const& value)
317
0
{
318
0
  auto reLanguageLevel = []() -> cmsys::RegularExpression {
319
0
    static cmsys::RegularExpression re{ "^[Cc]([+][+])?([0-9][0-9])$" };
320
0
    return re;
321
0
  }();
322
323
0
  if (reLanguageLevel.find(value)) {
324
0
    std::string::size_type const n = reLanguageLevel.end() - 2;
325
0
    cm::string_view const featurePrefix = (n == 3 ? "cxx_std_"_s : "c_std_"_s);
326
0
    if (configuration.empty()) {
327
0
      AppendProperty(makefile, target, "COMPILE_FEATURES"_s, {},
328
0
                     cmStrCat(featurePrefix, value.substr(n)));
329
0
    } else {
330
0
      std::string const& feature =
331
0
        cmStrCat("$<$<CONFIG:"_s, configuration, ">:"_s, featurePrefix,
332
0
                 value.substr(n), '>');
333
0
      AppendProperty(makefile, target, "COMPILE_FEATURES"_s, {}, feature);
334
0
    }
335
0
  } else if (cmStrCaseEq(value, "gnu"_s)) {
336
    // Not implemented in CMake at this time
337
0
  } else if (cmStrCaseEq(value, "threads"_s)) {
338
0
    AppendProperty(makefile, target, "LINK_LIBRARIES"_s, configuration,
339
0
                   "Threads::Threads");
340
0
  }
341
0
}
342
343
void AddLinkFeature(cmMakefile* makefile, cmTarget* target,
344
                    cm::string_view configuration, std::string const& value)
345
0
{
346
0
  if (cmStrCaseEq(value, "thread"_s)) {
347
0
    AppendProperty(makefile, target, "LINK_LIBRARIES"_s, configuration,
348
0
                   "Threads::Threads");
349
0
  }
350
0
}
351
352
std::string BuildDefinition(std::string const& name, Json::Value const& value)
353
0
{
354
0
  if (!value.isNull() && value.isConvertibleTo(Json::stringValue)) {
355
0
    return cmStrCat(name, '=', value.asString());
356
0
  }
357
0
  return name;
358
0
}
359
360
void AddDefinition(cmMakefile* makefile, cmTarget* target,
361
                   cm::string_view configuration,
362
                   std::string const& definition)
363
0
{
364
0
  AppendProperty(makefile, target, "COMPILE_DEFINITIONS"_s, configuration,
365
0
                 definition);
366
0
}
367
368
using DefinitionLanguageMap = std::map<cm::string_view, Json::Value>;
369
using DefinitionsMap = std::map<std::string, DefinitionLanguageMap>;
370
371
void AddDefinitions(cmMakefile* makefile, cmTarget* target,
372
                    cm::string_view configuration,
373
                    DefinitionsMap const& definitions)
374
0
{
375
0
  for (auto const& di : definitions) {
376
0
    auto const& g = di.second.find("*"_s);
377
0
    if (g != di.second.end()) {
378
0
      std::string const& def = BuildDefinition(di.first, g->second);
379
0
      if (di.second.size() == 1) {
380
        // Only the non-language-specific definition exists.
381
0
        AddDefinition(makefile, target, configuration, def);
382
0
        continue;
383
0
      }
384
385
      // Create a genex to apply this definition to all languages except
386
      // those that override it.
387
0
      std::vector<cm::string_view> excludedLanguages;
388
0
      for (auto const& li : di.second) {
389
0
        if (li.first != "*"_s) {
390
0
          excludedLanguages.emplace_back(li.first);
391
0
        }
392
0
      }
393
0
      AddDefinition(makefile, target, configuration,
394
0
                    cmStrCat("$<$<NOT:$<COMPILE_LANGUAGE:"_s,
395
0
                             cmJoin(excludedLanguages, ","_s), ">>:"_s, def,
396
0
                             '>'));
397
0
    }
398
399
    // Add language-specific definitions.
400
0
    for (auto const& li : di.second) {
401
0
      if (li.first != "*"_s) {
402
0
        AddDefinition(makefile, target, configuration,
403
0
                      cmStrCat("$<$<COMPILE_LANGUAGE:"_s, li.first, ">:"_s,
404
0
                               BuildDefinition(di.first, li.second), '>'));
405
0
      }
406
0
    }
407
0
  }
408
0
}
409
410
cm::optional<cmPackageInfoReader::Pep440Version> ParseSimpleVersion(
411
  std::string const& version)
412
0
{
413
0
  if (version.empty()) {
414
0
    return cm::nullopt;
415
0
  }
416
417
0
  cmPackageInfoReader::Pep440Version result;
418
0
  result.Simple = true;
419
420
0
  cm::string_view remnant{ version };
421
0
  for (;;) {
422
    // Find the next part separator.
423
0
    std::string::size_type const n = remnant.find_first_of(".+-"_s);
424
0
    if (n == 0) {
425
      // The part is an empty string.
426
0
      return cm::nullopt;
427
0
    }
428
429
    // Extract the part as a number.
430
0
    cm::string_view const part = remnant.substr(0, n);
431
0
    std::string::size_type const l = part.size();
432
0
    std::string::size_type p;
433
0
    unsigned long const value = std::stoul(std::string{ part }, &p);
434
0
    if (p != l || value > std::numeric_limits<unsigned>::max()) {
435
      // The part was not a valid number or is too big.
436
0
      return cm::nullopt;
437
0
    }
438
0
    result.ReleaseComponents.push_back(static_cast<unsigned>(value));
439
440
    // Have we consumed the entire input?
441
0
    if (n == std::string::npos) {
442
0
      return { std::move(result) };
443
0
    }
444
445
    // Lop off the current part.
446
0
    char const sep = remnant[n];
447
0
    remnant = remnant.substr(n + 1);
448
0
    if (sep == '+' || sep == '-') {
449
      // If we hit the local label, we're done.
450
0
      result.LocalLabel = remnant;
451
0
      return { std::move(result) };
452
0
    }
453
454
    // We just consumed a '.'; check that there's more.
455
0
    if (remnant.empty()) {
456
      // A trailing part separator is not allowed.
457
0
      return cm::nullopt;
458
0
    }
459
460
    // Continue with the remaining input.
461
0
  }
462
463
  // Unreachable.
464
0
}
465
466
} // namespace
467
468
std::unique_ptr<cmPackageInfoReader> cmPackageInfoReader::Read(
469
  cmMakefile* makefile, std::string const& path,
470
  cmPackageInfoReader const* parent)
471
0
{
472
  // Read file and perform some basic validation:
473
  //   - the input is valid JSON
474
  //   - the input is a JSON object
475
  //   - the input has a "cps_version" that we (in theory) know how to parse
476
0
  Json::Value data = ReadJson(path);
477
0
  if (!data.isObject() || (!parent && !CheckSchemaVersion(data))) {
478
0
    return nullptr;
479
0
  }
480
481
  //   - the input has a "name" attribute that is a non-empty string
482
0
  Json::Value const& name = data["name"];
483
0
  if (!name.isString() || name.empty()) {
484
0
    return nullptr;
485
0
  }
486
487
  //   - the input has a "components" attribute that is a JSON object
488
0
  if (!data["components"].isObject()) {
489
0
    return nullptr;
490
0
  }
491
492
0
  std::string prefix = (parent ? parent->Prefix : DeterminePrefix(path, data));
493
0
  if (prefix.empty()) {
494
0
    return nullptr;
495
0
  }
496
497
  // Seems sane enough to hand back to the caller.
498
0
  std::unique_ptr<cmPackageInfoReader> reader{ new cmPackageInfoReader };
499
0
  reader->Data = std::move(data);
500
0
  reader->Prefix = std::move(prefix);
501
0
  reader->Path = path;
502
503
  // Determine other information we need to know immediately, or (if this is
504
  // a supplemental reader) copy from the parent.
505
0
  if (parent) {
506
0
    reader->ComponentTargets = parent->ComponentTargets;
507
0
    reader->DefaultConfigurations = parent->DefaultConfigurations;
508
0
  } else {
509
0
    for (std::string const& config :
510
0
         ReadList(reader->Data, "configurations")) {
511
0
      reader->DefaultConfigurations.emplace_back(
512
0
        cmSystemTools::UpperCase(config));
513
0
    }
514
0
  }
515
516
  // Check for a default license.
517
0
  Json::Value const& defaultLicense = reader->Data["default_license"];
518
0
  if (!defaultLicense.isNull()) {
519
0
    if (defaultLicense.isString()) {
520
0
      reader->DefaultLicense = defaultLicense.asString();
521
0
    } else {
522
0
      makefile->IssueMessage(
523
0
        MessageType::WARNING,
524
0
        "Package attribute \"default_license\" is not a string.");
525
0
    }
526
0
  } else if (parent) {
527
0
    reader->DefaultLicense = parent->DefaultLicense;
528
0
  } else {
529
    // If there is no 'default_license', check for 'license'. Note that we
530
    // intentionally allow `default_license` on an appendix to override the
531
    // parent, but we do not consider `license` on an appendix. This is
532
    // consistent with not allowing LICENSE and APPENDIX to be used together.
533
0
    Json::Value const& packageLicense = reader->Data["license"];
534
0
    if (!packageLicense.isNull()) {
535
0
      if (packageLicense.isString()) {
536
0
        reader->DefaultLicense = packageLicense.asString();
537
0
      } else {
538
0
        makefile->IssueMessage(
539
0
          MessageType::WARNING,
540
0
          "Package attribute \"license\" is not a string.");
541
0
      }
542
0
    }
543
0
  }
544
545
0
  return reader;
546
0
}
547
548
std::string cmPackageInfoReader::GetName() const
549
0
{
550
0
  return ToString(this->Data["name"]);
551
0
}
552
553
cm::optional<std::string> cmPackageInfoReader::GetVersion() const
554
0
{
555
0
  Json::Value const& version = this->Data["version"];
556
0
  if (version.isString()) {
557
0
    return version.asString();
558
0
  }
559
0
  return cm::nullopt;
560
0
}
561
562
cm::optional<std::string> cmPackageInfoReader::GetCompatVersion() const
563
0
{
564
0
  Json::Value const& version = this->Data["compat_version"];
565
0
  if (version.isString()) {
566
0
    return version.asString();
567
0
  }
568
0
  return cm::nullopt;
569
0
}
570
571
cm::optional<cmPackageInfoReader::Pep440Version>
572
cmPackageInfoReader::ParseVersion(
573
  cm::optional<std::string> const& version) const
574
0
{
575
  // Check that we have a version.
576
0
  if (!version) {
577
0
    return cm::nullopt;
578
0
  }
579
580
  // Check if we know how to parse the version.
581
0
  Json::Value const& schema = this->Data["version_schema"];
582
0
  if (schema.isNull() || cmStrCaseEq(ToString(schema), "simple"_s)) {
583
0
    return ParseSimpleVersion(*version);
584
0
  }
585
586
0
  return cm::nullopt;
587
0
}
588
589
std::vector<cmPackageRequirement> cmPackageInfoReader::GetRequirements() const
590
0
{
591
0
  std::vector<cmPackageRequirement> requirements;
592
593
0
  auto const& requirementObjects = this->Data["requires"];
594
595
0
  for (auto ri = requirementObjects.begin(), re = requirementObjects.end();
596
0
       ri != re; ++ri) {
597
0
    cmPackageRequirement r{ ri.name(), ToString((*ri)["version"]),
598
0
                            ReadList(*ri, "components"),
599
0
                            ReadList(*ri, "hints") };
600
0
    requirements.emplace_back(std::move(r));
601
0
  }
602
603
0
  return requirements;
604
0
}
605
606
std::vector<std::string> cmPackageInfoReader::GetComponentNames() const
607
0
{
608
0
  std::vector<std::string> componentNames;
609
610
0
  Json::Value const& components = this->Data["components"];
611
0
  for (auto ci = components.begin(), ce = components.end(); ci != ce; ++ci) {
612
0
    componentNames.emplace_back(ci.name());
613
0
  }
614
615
0
  return componentNames;
616
0
}
617
618
std::string cmPackageInfoReader::ResolvePath(std::string path) const
619
0
{
620
0
  cmSystemTools::ConvertToUnixSlashes(path);
621
0
  if (cmHasPrefix(path, "@prefix@"_s)) {
622
0
    return cmStrCat(this->Prefix, path.substr(8));
623
0
  }
624
0
  if (!cmSystemTools::FileIsFullPath(path)) {
625
0
    return cmStrCat(cmSystemTools::GetFilenamePath(this->Path), '/', path);
626
0
  }
627
0
  return path;
628
0
}
629
630
void cmPackageInfoReader::AddTargetConfiguration(
631
  cmTarget* target, cm::string_view configuration) const
632
0
{
633
0
  static std::string const icProp = "IMPORTED_CONFIGURATIONS";
634
635
0
  std::string const& configUpper = cmSystemTools::UpperCase(configuration);
636
637
  // Get existing list of imported configurations.
638
0
  cmList configs;
639
0
  if (cmValue v = target->GetProperty(icProp)) {
640
0
    configs.assign(cmSystemTools::UpperCase(*v));
641
0
  } else {
642
    // If the existing list is empty, just add the new one and return.
643
0
    target->SetProperty(icProp, configUpper);
644
0
    return;
645
0
  }
646
647
0
  if (cm::contains(configs, configUpper)) {
648
    // If the configuration is already listed, we don't need to do anything.
649
0
    return;
650
0
  }
651
652
  // Add the new configuration.
653
0
  configs.append(configUpper);
654
655
  // Rebuild the configuration list by extracting any configuration in the
656
  // default configurations and reinserting it at the beginning of the list
657
  // according to the order of the default configurations.
658
0
  std::vector<std::string> newConfigs;
659
0
  for (std::string const& c : this->DefaultConfigurations) {
660
0
    auto ci = std::find(configs.begin(), configs.end(), c);
661
0
    if (ci != configs.end()) {
662
0
      newConfigs.emplace_back(std::move(*ci));
663
0
      configs.erase(ci);
664
0
    }
665
0
  }
666
0
  for (std::string& c : configs) {
667
0
    newConfigs.emplace_back(std::move(c));
668
0
  }
669
670
0
  target->SetProperty("IMPORTED_CONFIGURATIONS", cmJoin(newConfigs, ";"_s));
671
0
}
672
673
void cmPackageInfoReader::SetImportProperty(cmMakefile* makefile,
674
                                            cmTarget* target,
675
                                            cm::string_view property,
676
                                            cm::string_view configuration,
677
                                            Json::Value const& object,
678
                                            std::string const& attribute) const
679
0
{
680
0
  Json::Value const& value = object[attribute];
681
0
  if (!value.isNull()) {
682
0
    std::string fullprop;
683
0
    if (configuration.empty()) {
684
0
      fullprop = cmStrCat("IMPORTED_"_s, property);
685
0
    } else {
686
0
      fullprop = cmStrCat("IMPORTED_"_s, property, '_',
687
0
                          cmSystemTools::UpperCase(configuration));
688
0
    }
689
690
0
    if (value.isString()) {
691
0
      target->SetProperty(fullprop, this->ResolvePath(value.asString()));
692
0
    } else {
693
0
      makefile->IssueMessage(MessageType::WARNING,
694
0
                             cmStrCat("Failed to set property \""_s, property,
695
0
                                      "\" on target \""_s, target->GetName(),
696
0
                                      "\": attribute \"", attribute,
697
0
                                      "\" is not a string."_s));
698
0
    }
699
0
  }
700
0
}
701
702
void cmPackageInfoReader::SetMetaProperty(
703
  cmMakefile* makefile, cmTarget* target, std::string const& property,
704
  Json::Value const& object, std::string const& attribute,
705
  std::string const& defaultValue) const
706
0
{
707
0
  Json::Value const& value = object[attribute];
708
0
  if (!value.isNull()) {
709
0
    if (value.isString()) {
710
0
      target->SetProperty(property, value.asString());
711
0
    } else {
712
0
      makefile->IssueMessage(MessageType::WARNING,
713
0
                             cmStrCat("Failed to set property \""_s, property,
714
0
                                      "\" on target \""_s, target->GetName(),
715
0
                                      "\": attribute \"", attribute,
716
0
                                      "\" is not a string."_s));
717
0
    }
718
0
  } else if (!defaultValue.empty()) {
719
0
    target->SetProperty(property, defaultValue);
720
0
  }
721
0
}
722
723
void cmPackageInfoReader::SetTargetProperties(
724
  cmMakefile* makefile, cmTarget* target, Json::Value const& data,
725
  std::string const& package, cm::string_view configuration) const
726
0
{
727
  // Add configuration (if applicable).
728
0
  if (!configuration.empty()) {
729
0
    this->AddTargetConfiguration(target, configuration);
730
0
  }
731
732
  // Add compile and link features.
733
0
  for (std::string const& def : ReadList(data, "compile_features")) {
734
0
    AddCompileFeature(makefile, target, configuration, def);
735
0
  }
736
737
0
  for (std::string const& def : ReadList(data, "link_features")) {
738
0
    AddLinkFeature(makefile, target, configuration, def);
739
0
  }
740
741
  // Add compile definitions.
742
0
  Json::Value const& defs = data["definitions"];
743
0
  DefinitionsMap definitionsMap;
744
0
  for (auto ldi = defs.begin(), lde = defs.end(); ldi != lde; ++ldi) {
745
0
    cm::string_view const originalLang = IterKey(ldi);
746
0
    cm::string_view const lang = MapLanguage(originalLang);
747
0
    if (lang.empty()) {
748
0
      makefile->IssueMessage(
749
0
        MessageType::WARNING,
750
0
        cmStrCat(R"(ignoring unknown language ")"_s, originalLang,
751
0
                 R"(" in definitions for )"_s, target->GetName()));
752
0
      continue;
753
0
    }
754
755
0
    for (auto di = ldi->begin(), de = ldi->end(); di != de; ++di) {
756
0
      definitionsMap[di.name()].emplace(lang, *di);
757
0
    }
758
0
  }
759
0
  AddDefinitions(makefile, target, configuration, definitionsMap);
760
761
  // Add include directories.
762
0
  AppendLanguageProperties(makefile, target, "INCLUDE_DIRECTORIES"_s,
763
0
                           configuration, data, "includes",
764
0
                           [this](std::string p) -> std::string {
765
0
                             return this->ResolvePath(std::move(p));
766
0
                           });
767
768
  // Add link name/location(s).
769
0
  this->SetImportProperty(makefile, target, "LOCATION"_s, // br
770
0
                          configuration, data, "location");
771
772
0
  this->SetImportProperty(makefile, target, "IMPLIB"_s, // br
773
0
                          configuration, data, "link_location");
774
775
0
  this->SetImportProperty(makefile, target, "SONAME"_s, // br
776
0
                          configuration, data, "link_name");
777
778
  // Add link languages.
779
0
  for (std::string const& originalLang : ReadList(data, "link_languages")) {
780
0
    cm::string_view const lang = MapLanguage(originalLang, DisallowGlob);
781
0
    if (!lang.empty()) {
782
0
      AppendProperty(makefile, target, "LINK_LANGUAGES"_s, configuration,
783
0
                     std::string{ lang });
784
0
    }
785
0
  }
786
787
  // Add transitive dependencies.
788
0
  for (std::string const& dep : ReadList(data, "requires")) {
789
0
    AppendProperty(makefile, target, "LINK_LIBRARIES"_s, configuration,
790
0
                   NormalizeTargetName(dep, package));
791
0
  }
792
793
0
  for (std::string const& dep : ReadList(data, "compile_requires")) {
794
0
    std::string const& lib =
795
0
      cmStrCat("$<COMPILE_ONLY:"_s, NormalizeTargetName(dep, package), '>');
796
0
    AppendProperty(makefile, target, "LINK_LIBRARIES"_s, configuration, lib);
797
0
  }
798
799
0
  for (std::string const& dep : ReadList(data, "link_requires")) {
800
0
    std::string const& lib =
801
0
      cmStrCat("$<LINK_ONLY:"_s, NormalizeTargetName(dep, package), '>');
802
0
    AppendProperty(makefile, target, "LINK_LIBRARIES"_s, configuration, lib);
803
0
  }
804
805
0
  for (std::string const& dep : ReadList(data, "dyld_requires")) {
806
0
    AppendImportProperty(makefile, target, "LINK_DEPENDENT_LIBRARIES"_s,
807
0
                         configuration, NormalizeTargetName(dep, package));
808
0
  }
809
810
0
  for (std::string const& lib : ReadList(data, "link_libraries")) {
811
0
    AppendProperty(makefile, target, "LINK_LIBRARIES"_s, configuration, lib);
812
0
  }
813
814
  // TODO: Handle non-configuration modules
815
  // once IMPORTED_CXX_MODULES supports it
816
0
  if (!configuration.empty()) {
817
0
    this->ReadCxxModulesMetadata(makefile, target, configuration, data);
818
0
  }
819
820
  // Add other information.
821
0
  if (configuration.empty()) {
822
0
    this->SetMetaProperty(makefile, target, "SPDX_LICENSE", data, "license",
823
0
                          this->DefaultLicense);
824
0
  }
825
0
}
826
827
void cmPackageInfoReader::ReadCxxModulesMetadata(
828
  cmMakefile* makefile, cmTarget* target, cm::string_view configuration,
829
  Json::Value const& object) const
830
0
{
831
0
#ifndef CMAKE_BOOTSTRAP
832
0
  Json::Value const& path = object["cpp_module_metadata"];
833
834
0
  if (!path.isString()) {
835
0
    return;
836
0
  }
837
838
0
  cmCxxModuleMetadata::ParseResult result =
839
0
    cmCxxModuleMetadata::LoadFromFile(this->ResolvePath(path.asString()));
840
841
0
  if (!result) {
842
0
    makefile->IssueMessage(
843
0
      MessageType::WARNING,
844
0
      cmStrCat("Error parsing module manifest:\n"_s, result.Error));
845
0
    return;
846
0
  }
847
848
0
  cmCxxModuleMetadata::PopulateTarget(*target, *result.Meta, configuration);
849
0
#endif
850
0
}
851
852
cmTarget* cmPackageInfoReader::AddLibraryComponent(
853
  cmMakefile* makefile, cmStateEnums::TargetType type, std::string const& name,
854
  Json::Value const& data, std::string const& package, bool global) const
855
0
{
856
  // Create the imported target.
857
0
  cmTarget* const target = makefile->AddImportedTarget(name, type, global);
858
0
  target->SetOrigin(cmTarget::Origin::Cps);
859
860
  // Set target properties.
861
0
  this->SetTargetProperties(makefile, target, data, package, {});
862
0
  auto const& cfgData = data["configurations"];
863
0
  for (auto ci = cfgData.begin(), ce = cfgData.end(); ci != ce; ++ci) {
864
0
    this->SetTargetProperties(makefile, target, *ci, package, IterKey(ci));
865
0
  }
866
867
0
  return target;
868
0
}
869
870
bool cmPackageInfoReader::ImportTargets(cmMakefile* makefile,
871
                                        cmExecutionStatus& status, bool global)
872
0
{
873
0
  std::string const& package = this->GetName();
874
875
  // Read components.
876
0
  Json::Value const& components = this->Data["components"];
877
878
0
  for (auto ci = components.begin(), ce = components.end(); ci != ce; ++ci) {
879
0
    cm::string_view const name = IterKey(ci);
880
0
    std::string const& type =
881
0
      cmSystemTools::LowerCase(ToString((*ci)["type"]));
882
883
    // Get and validate full target name.
884
0
    std::string const& fullName = cmStrCat(package, "::"_s, name);
885
0
    {
886
0
      std::string msg;
887
0
      if (!makefile->EnforceUniqueName(fullName, msg)) {
888
0
        status.SetError(msg);
889
0
        return false;
890
0
      }
891
0
    }
892
893
0
    auto createTarget = [&](cmStateEnums::TargetType typeEnum) {
894
0
      return this->AddLibraryComponent(makefile, typeEnum, fullName, *ci,
895
0
                                       package, global);
896
0
    };
897
898
0
    cmTarget* target = nullptr;
899
0
    if (type == "symbolic"_s) {
900
0
      target = createTarget(cmStateEnums::INTERFACE_LIBRARY);
901
0
      target->SetSymbolic(true);
902
0
    } else if (type == "dylib"_s) {
903
0
      target = createTarget(cmStateEnums::SHARED_LIBRARY);
904
0
    } else if (type == "module"_s) {
905
0
      target = createTarget(cmStateEnums::MODULE_LIBRARY);
906
0
    } else if (type == "archive"_s) {
907
0
      target = createTarget(cmStateEnums::STATIC_LIBRARY);
908
0
    } else if (type == "interface"_s) {
909
0
      target = createTarget(cmStateEnums::INTERFACE_LIBRARY);
910
0
    } else {
911
0
      makefile->IssueMessage(MessageType::WARNING,
912
0
                             cmStrCat(R"(component ")"_s, fullName,
913
0
                                      R"(" has unknown type ")"_s, type,
914
0
                                      R"(" and was not imported)"_s));
915
0
    }
916
917
0
    if (target) {
918
0
      this->ComponentTargets.emplace(std::string{ name }, target);
919
0
    }
920
0
  }
921
922
  // Read default components.
923
0
  std::vector<std::string> const& defaultComponents =
924
0
    ReadList(this->Data, "default_components");
925
0
  if (!defaultComponents.empty()) {
926
0
    std::string msg;
927
0
    if (!makefile->EnforceUniqueName(package, msg)) {
928
0
      status.SetError(msg);
929
0
      return false;
930
0
    }
931
932
0
    cmTarget* const target = makefile->AddImportedTarget(
933
0
      package, cmStateEnums::INTERFACE_LIBRARY, global);
934
0
    for (std::string const& name : defaultComponents) {
935
0
      std::string const& fullName = cmStrCat(package, "::"_s, name);
936
0
      AppendProperty(makefile, target, "LINK_LIBRARIES"_s, {}, fullName);
937
0
    }
938
0
  }
939
940
0
  return true;
941
0
}
942
943
bool cmPackageInfoReader::ImportTargetConfigurations(
944
  cmMakefile* makefile, cmExecutionStatus& status) const
945
0
{
946
0
  std::string const& configuration = ToString(this->Data["configuration"]);
947
948
0
  if (configuration.empty()) {
949
0
    makefile->IssueMessage(MessageType::WARNING,
950
0
                           cmStrCat("supplemental file "_s, this->Path,
951
0
                                    " does not specify a configuration"_s));
952
0
    return true;
953
0
  }
954
955
0
  std::string const& package = this->GetName();
956
0
  Json::Value const& components = this->Data["components"];
957
958
0
  for (auto ci = components.begin(), ce = components.end(); ci != ce; ++ci) {
959
    // Get component name and look up target.
960
0
    cm::string_view const name = IterKey(ci);
961
0
    auto const& ti = this->ComponentTargets.find(std::string{ name });
962
0
    if (ti == this->ComponentTargets.end()) {
963
0
      status.SetError(cmStrCat("component "_s, name, " was not found"_s));
964
0
      return false;
965
0
    }
966
967
    // Read supplemental data for component.
968
0
    this->SetTargetProperties(makefile, ti->second, *ci, package,
969
0
                              configuration);
970
0
  }
971
972
0
  return true;
973
0
}