Coverage Report

Created: 2026-02-09 06:05

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