1
0

cmPackageInfoReader.cxx 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
  2. file Copyright.txt or https://cmake.org/licensing for details. */
  3. #include "cmPackageInfoReader.h"
  4. #include <limits>
  5. #include <cm/string_view>
  6. #include <cmext/string_view>
  7. #include <cm3p/json/reader.h>
  8. #include <cm3p/json/value.h>
  9. #include <cm3p/json/version.h>
  10. #include "cmsys/FStream.hxx"
  11. #include "cmStringAlgorithms.h"
  12. #include "cmSystemTools.h"
  13. namespace {
  14. Json::Value ReadJson(std::string const& fileName)
  15. {
  16. // Open the specified file.
  17. cmsys::ifstream file(fileName.c_str(), std::ios::in | std::ios::binary);
  18. if (!file) {
  19. #if JSONCPP_VERSION_HEXA < 0x01070300
  20. return Json::Value::null;
  21. #else
  22. return Json::Value::nullSingleton();
  23. #endif
  24. }
  25. // Read file content and translate JSON.
  26. Json::Value data;
  27. Json::CharReaderBuilder builder;
  28. builder["collectComments"] = false;
  29. if (!Json::parseFromStream(builder, file, &data, nullptr)) {
  30. #if JSONCPP_VERSION_HEXA < 0x01070300
  31. return Json::Value::null;
  32. #else
  33. return Json::Value::nullSingleton();
  34. #endif
  35. }
  36. return data;
  37. }
  38. bool CheckSchemaVersion(Json::Value const& data)
  39. {
  40. std::string const& version = data["cps_version"].asString();
  41. // Check that a valid version is specified.
  42. if (version.empty()) {
  43. return false;
  44. }
  45. // Check that we understand this version.
  46. return cmSystemTools::VersionCompare(cmSystemTools::OP_GREATER_EQUAL,
  47. version, "0.12") &&
  48. cmSystemTools::VersionCompare(cmSystemTools::OP_LESS, version, "1");
  49. // TODO Eventually this probably needs to return the version tuple, and
  50. // should share code with cmPackageInfoReader::ParseVersion.
  51. }
  52. } // namespace
  53. std::unique_ptr<cmPackageInfoReader> cmPackageInfoReader::Read(
  54. std::string const& path)
  55. {
  56. // Read file and perform some basic validation:
  57. // - the input is valid JSON
  58. // - the input is a JSON object
  59. // - the input has a "cps_version" that we (in theory) know how to parse
  60. Json::Value data = ReadJson(path);
  61. if (!data.isObject() || !CheckSchemaVersion(data)) {
  62. return nullptr;
  63. }
  64. // - the input has a "name" attribute that is a non-empty string
  65. Json::Value const& name = data["name"];
  66. if (!name.isString() || name.empty()) {
  67. return nullptr;
  68. }
  69. // - the input has a "components" attribute that is a JSON object
  70. if (!data["components"].isObject()) {
  71. return nullptr;
  72. }
  73. // Seems sane enough to hand back to the caller.
  74. std::unique_ptr<cmPackageInfoReader> reader{ new cmPackageInfoReader };
  75. reader->Data = data;
  76. return reader;
  77. }
  78. std::string cmPackageInfoReader::GetName() const
  79. {
  80. return this->Data["name"].asString();
  81. }
  82. cm::optional<std::string> cmPackageInfoReader::GetVersion() const
  83. {
  84. Json::Value const& version = this->Data["version"];
  85. if (version.isString()) {
  86. return version.asString();
  87. }
  88. return cm::nullopt;
  89. }
  90. std::vector<unsigned> cmPackageInfoReader::ParseVersion() const
  91. {
  92. // Check that we have a version.
  93. cm::optional<std::string> const& version = this->GetVersion();
  94. if (!version) {
  95. return {};
  96. }
  97. std::vector<unsigned> result;
  98. cm::string_view remnant{ *version };
  99. // Check if we know how to parse the version.
  100. Json::Value const& schema = this->Data["version_schema"];
  101. if (schema.isNull() || cmStrCaseEq(schema.asString(), "simple"_s)) {
  102. // Keep going until we run out of parts.
  103. while (!remnant.empty()) {
  104. std::string::size_type n = remnant.find('.');
  105. cm::string_view part = remnant.substr(0, n);
  106. if (n == std::string::npos) {
  107. remnant = {};
  108. } else {
  109. remnant = remnant.substr(n + 1);
  110. }
  111. unsigned long const value = std::stoul(std::string{ part }, &n);
  112. if (n == 0 || value > std::numeric_limits<unsigned>::max()) {
  113. // The part was not a valid number or is too big.
  114. return {};
  115. }
  116. result.push_back(static_cast<unsigned>(value));
  117. }
  118. }
  119. return result;
  120. }