Coverage Report

Created: 2026-07-30 06:52

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