|
@@ -0,0 +1,146 @@
|
|
|
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
|
|
|
+ file Copyright.txt or https://cmake.org/licensing for details. */
|
|
|
+#include "cmPackageInfoReader.h"
|
|
|
+
|
|
|
+#include <limits>
|
|
|
+
|
|
|
+#include <cm/string_view>
|
|
|
+#include <cmext/string_view>
|
|
|
+
|
|
|
+#include <cm3p/json/reader.h>
|
|
|
+#include <cm3p/json/value.h>
|
|
|
+#include <cm3p/json/version.h>
|
|
|
+
|
|
|
+#include "cmsys/FStream.hxx"
|
|
|
+
|
|
|
+#include "cmStringAlgorithms.h"
|
|
|
+#include "cmSystemTools.h"
|
|
|
+
|
|
|
+namespace {
|
|
|
+
|
|
|
+Json::Value ReadJson(std::string const& fileName)
|
|
|
+{
|
|
|
+ // Open the specified file.
|
|
|
+ cmsys::ifstream file(fileName.c_str(), std::ios::in | std::ios::binary);
|
|
|
+ if (!file) {
|
|
|
+#if JSONCPP_VERSION_HEXA < 0x01070300
|
|
|
+ return Json::Value::null;
|
|
|
+#else
|
|
|
+ return Json::Value::nullSingleton();
|
|
|
+#endif
|
|
|
+ }
|
|
|
+
|
|
|
+ // Read file content and translate JSON.
|
|
|
+ Json::Value data;
|
|
|
+ Json::CharReaderBuilder builder;
|
|
|
+ builder["collectComments"] = false;
|
|
|
+ if (!Json::parseFromStream(builder, file, &data, nullptr)) {
|
|
|
+#if JSONCPP_VERSION_HEXA < 0x01070300
|
|
|
+ return Json::Value::null;
|
|
|
+#else
|
|
|
+ return Json::Value::nullSingleton();
|
|
|
+#endif
|
|
|
+ }
|
|
|
+
|
|
|
+ return data;
|
|
|
+}
|
|
|
+
|
|
|
+bool CheckSchemaVersion(Json::Value const& data)
|
|
|
+{
|
|
|
+ std::string const& version = data["cps_version"].asString();
|
|
|
+
|
|
|
+ // Check that a valid version is specified.
|
|
|
+ if (version.empty()) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Check that we understand this version.
|
|
|
+ return cmSystemTools::VersionCompare(cmSystemTools::OP_GREATER_EQUAL,
|
|
|
+ version, "0.12") &&
|
|
|
+ cmSystemTools::VersionCompare(cmSystemTools::OP_LESS, version, "1");
|
|
|
+
|
|
|
+ // TODO Eventually this probably needs to return the version tuple, and
|
|
|
+ // should share code with cmPackageInfoReader::ParseVersion.
|
|
|
+}
|
|
|
+
|
|
|
+} // namespace
|
|
|
+
|
|
|
+std::unique_ptr<cmPackageInfoReader> cmPackageInfoReader::Read(
|
|
|
+ std::string const& path)
|
|
|
+{
|
|
|
+ // Read file and perform some basic validation:
|
|
|
+ // - the input is valid JSON
|
|
|
+ // - the input is a JSON object
|
|
|
+ // - the input has a "cps_version" that we (in theory) know how to parse
|
|
|
+ Json::Value data = ReadJson(path);
|
|
|
+ if (!data.isObject() || !CheckSchemaVersion(data)) {
|
|
|
+ return nullptr;
|
|
|
+ }
|
|
|
+
|
|
|
+ // - the input has a "name" attribute that is a non-empty string
|
|
|
+ Json::Value const& name = data["name"];
|
|
|
+ if (!name.isString() || name.empty()) {
|
|
|
+ return nullptr;
|
|
|
+ }
|
|
|
+
|
|
|
+ // - the input has a "components" attribute that is a JSON object
|
|
|
+ if (!data["components"].isObject()) {
|
|
|
+ return nullptr;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Seems sane enough to hand back to the caller.
|
|
|
+ std::unique_ptr<cmPackageInfoReader> reader{ new cmPackageInfoReader };
|
|
|
+ reader->Data = data;
|
|
|
+
|
|
|
+ return reader;
|
|
|
+}
|
|
|
+
|
|
|
+std::string cmPackageInfoReader::GetName() const
|
|
|
+{
|
|
|
+ return this->Data["name"].asString();
|
|
|
+}
|
|
|
+
|
|
|
+cm::optional<std::string> cmPackageInfoReader::GetVersion() const
|
|
|
+{
|
|
|
+ Json::Value const& version = this->Data["version"];
|
|
|
+ if (version.isString()) {
|
|
|
+ return version.asString();
|
|
|
+ }
|
|
|
+ return cm::nullopt;
|
|
|
+}
|
|
|
+
|
|
|
+std::vector<unsigned> cmPackageInfoReader::ParseVersion() const
|
|
|
+{
|
|
|
+ // Check that we have a version.
|
|
|
+ cm::optional<std::string> const& version = this->GetVersion();
|
|
|
+ if (!version) {
|
|
|
+ return {};
|
|
|
+ }
|
|
|
+
|
|
|
+ std::vector<unsigned> result;
|
|
|
+ cm::string_view remnant{ *version };
|
|
|
+
|
|
|
+ // Check if we know how to parse the version.
|
|
|
+ Json::Value const& schema = this->Data["version_schema"];
|
|
|
+ if (schema.isNull() || cmStrCaseEq(schema.asString(), "simple"_s)) {
|
|
|
+ // Keep going until we run out of parts.
|
|
|
+ while (!remnant.empty()) {
|
|
|
+ std::string::size_type n = remnant.find('.');
|
|
|
+ cm::string_view part = remnant.substr(0, n);
|
|
|
+ if (n == std::string::npos) {
|
|
|
+ remnant = {};
|
|
|
+ } else {
|
|
|
+ remnant = remnant.substr(n + 1);
|
|
|
+ }
|
|
|
+
|
|
|
+ unsigned long const value = std::stoul(std::string{ part }, &n);
|
|
|
+ if (n == 0 || value > std::numeric_limits<unsigned>::max()) {
|
|
|
+ // The part was not a valid number or is too big.
|
|
|
+ return {};
|
|
|
+ }
|
|
|
+ result.push_back(static_cast<unsigned>(value));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return result;
|
|
|
+}
|