Coverage Report

Created: 2026-07-14 07:09

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