Coverage Report

Created: 2026-09-14 06:43

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