Files
CMake/Source/cmCMakePresetsGraphReadJSON.cxx
Kyle Edwards a239f23a98 Refactor: Generalize file graph in CMakePresets
Before this refactoring, presets had a simple flag that marked them
as "user" or "not user", and checking the file graph of two files
was as simple as checking this flag. This only allowed for two files
in the graph. Generalize the code to allow for arbitrarily many files
in the graph.
2022-01-06 19:46:41 -05:00

588 lines
19 KiB
C++

/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file Copyright.txt or https://cmake.org/licensing for details. */
#include <algorithm>
#include <functional>
#include <map>
#include <string>
#include <unordered_set>
#include <utility>
#include <vector>
#include <cm/memory>
#include <cm/optional>
#include <cmext/string_view>
#include <cm3p/json/reader.h>
#include <cm3p/json/value.h>
#include "cmsys/FStream.hxx"
#include "cmCMakePresetsGraph.h"
#include "cmCMakePresetsGraphInternal.h"
#include "cmJSONHelpers.h"
#include "cmStringAlgorithms.h"
#include "cmSystemTools.h"
#include "cmVersion.h"
namespace {
using ReadFileResult = cmCMakePresetsGraph::ReadFileResult;
using CacheVariable = cmCMakePresetsGraph::CacheVariable;
using ConfigurePreset = cmCMakePresetsGraph::ConfigurePreset;
using BuildPreset = cmCMakePresetsGraph::BuildPreset;
using TestPreset = cmCMakePresetsGraph::TestPreset;
using ArchToolsetStrategy = cmCMakePresetsGraph::ArchToolsetStrategy;
constexpr int MIN_VERSION = 1;
constexpr int MAX_VERSION = 3;
struct CMakeVersion
{
unsigned int Major = 0;
unsigned int Minor = 0;
unsigned int Patch = 0;
};
struct RootPresets
{
CMakeVersion CMakeMinimumRequired;
std::vector<cmCMakePresetsGraph::ConfigurePreset> ConfigurePresets;
std::vector<cmCMakePresetsGraph::BuildPreset> BuildPresets;
std::vector<cmCMakePresetsGraph::TestPreset> TestPresets;
};
std::unique_ptr<cmCMakePresetsGraphInternal::NotCondition> InvertCondition(
std::unique_ptr<cmCMakePresetsGraph::Condition> condition)
{
auto retval = cm::make_unique<cmCMakePresetsGraphInternal::NotCondition>();
retval->SubCondition = std::move(condition);
return retval;
}
auto const ConditionStringHelper = cmJSONStringHelper<ReadFileResult>(
ReadFileResult::READ_OK, ReadFileResult::INVALID_CONDITION);
auto const ConditionBoolHelper = cmJSONBoolHelper<ReadFileResult>(
ReadFileResult::READ_OK, ReadFileResult::INVALID_CONDITION);
auto const ConditionStringListHelper =
cmJSONVectorHelper<std::string, ReadFileResult>(
ReadFileResult::READ_OK, ReadFileResult::INVALID_CONDITION,
ConditionStringHelper);
auto const ConstConditionHelper =
cmJSONObjectHelper<cmCMakePresetsGraphInternal::ConstCondition,
ReadFileResult>(ReadFileResult::READ_OK,
ReadFileResult::INVALID_CONDITION, false)
.Bind<std::string>("type"_s, nullptr, ConditionStringHelper, true)
.Bind("value"_s, &cmCMakePresetsGraphInternal::ConstCondition::Value,
ConditionBoolHelper, true);
auto const EqualsConditionHelper =
cmJSONObjectHelper<cmCMakePresetsGraphInternal::EqualsCondition,
ReadFileResult>(ReadFileResult::READ_OK,
ReadFileResult::INVALID_CONDITION, false)
.Bind<std::string>("type"_s, nullptr, ConditionStringHelper, true)
.Bind("lhs"_s, &cmCMakePresetsGraphInternal::EqualsCondition::Lhs,
ConditionStringHelper, true)
.Bind("rhs"_s, &cmCMakePresetsGraphInternal::EqualsCondition::Rhs,
ConditionStringHelper, true);
auto const InListConditionHelper =
cmJSONObjectHelper<cmCMakePresetsGraphInternal::InListCondition,
ReadFileResult>(ReadFileResult::READ_OK,
ReadFileResult::INVALID_CONDITION, false)
.Bind<std::string>("type"_s, nullptr, ConditionStringHelper, true)
.Bind("string"_s, &cmCMakePresetsGraphInternal::InListCondition::String,
ConditionStringHelper, true)
.Bind("list"_s, &cmCMakePresetsGraphInternal::InListCondition::List,
ConditionStringListHelper, true);
auto const MatchesConditionHelper =
cmJSONObjectHelper<cmCMakePresetsGraphInternal::MatchesCondition,
ReadFileResult>(ReadFileResult::READ_OK,
ReadFileResult::INVALID_CONDITION, false)
.Bind<std::string>("type"_s, nullptr, ConditionStringHelper, true)
.Bind("string"_s, &cmCMakePresetsGraphInternal::MatchesCondition::String,
ConditionStringHelper, true)
.Bind("regex"_s, &cmCMakePresetsGraphInternal::MatchesCondition::Regex,
ConditionStringHelper, true);
ReadFileResult SubConditionHelper(
std::unique_ptr<cmCMakePresetsGraph::Condition>& out,
const Json::Value* value);
auto const ListConditionVectorHelper =
cmJSONVectorHelper<std::unique_ptr<cmCMakePresetsGraph::Condition>,
ReadFileResult>(ReadFileResult::READ_OK,
ReadFileResult::INVALID_CONDITION,
SubConditionHelper);
auto const AnyAllOfConditionHelper =
cmJSONObjectHelper<cmCMakePresetsGraphInternal::AnyAllOfCondition,
ReadFileResult>(ReadFileResult::READ_OK,
ReadFileResult::INVALID_CONDITION, false)
.Bind<std::string>("type"_s, nullptr, ConditionStringHelper, true)
.Bind("conditions"_s,
&cmCMakePresetsGraphInternal::AnyAllOfCondition::Conditions,
ListConditionVectorHelper);
auto const NotConditionHelper =
cmJSONObjectHelper<cmCMakePresetsGraphInternal::NotCondition,
ReadFileResult>(ReadFileResult::READ_OK,
ReadFileResult::INVALID_CONDITION, false)
.Bind<std::string>("type"_s, nullptr, ConditionStringHelper, true)
.Bind("condition"_s,
&cmCMakePresetsGraphInternal::NotCondition::SubCondition,
SubConditionHelper);
ReadFileResult ConditionHelper(
std::unique_ptr<cmCMakePresetsGraph::Condition>& out,
const Json::Value* value)
{
if (!value) {
out.reset();
return ReadFileResult::READ_OK;
}
if (value->isBool()) {
auto c = cm::make_unique<cmCMakePresetsGraphInternal::ConstCondition>();
c->Value = value->asBool();
out = std::move(c);
return ReadFileResult::READ_OK;
}
if (value->isNull()) {
out = cm::make_unique<cmCMakePresetsGraphInternal::NullCondition>();
return ReadFileResult::READ_OK;
}
if (value->isObject()) {
if (!value->isMember("type")) {
return ReadFileResult::INVALID_CONDITION;
}
if (!(*value)["type"].isString()) {
return ReadFileResult::INVALID_CONDITION;
}
auto type = (*value)["type"].asString();
if (type == "const") {
auto c = cm::make_unique<cmCMakePresetsGraphInternal::ConstCondition>();
CHECK_OK(ConstConditionHelper(*c, value));
out = std::move(c);
return ReadFileResult::READ_OK;
}
if (type == "equals" || type == "notEquals") {
auto c = cm::make_unique<cmCMakePresetsGraphInternal::EqualsCondition>();
CHECK_OK(EqualsConditionHelper(*c, value));
out = std::move(c);
if (type == "notEquals") {
out = InvertCondition(std::move(out));
}
return ReadFileResult::READ_OK;
}
if (type == "inList" || type == "notInList") {
auto c = cm::make_unique<cmCMakePresetsGraphInternal::InListCondition>();
CHECK_OK(InListConditionHelper(*c, value));
out = std::move(c);
if (type == "notInList") {
out = InvertCondition(std::move(out));
}
return ReadFileResult::READ_OK;
}
if (type == "matches" || type == "notMatches") {
auto c =
cm::make_unique<cmCMakePresetsGraphInternal::MatchesCondition>();
CHECK_OK(MatchesConditionHelper(*c, value));
out = std::move(c);
if (type == "notMatches") {
out = InvertCondition(std::move(out));
}
return ReadFileResult::READ_OK;
}
if (type == "anyOf" || type == "allOf") {
auto c =
cm::make_unique<cmCMakePresetsGraphInternal::AnyAllOfCondition>();
c->StopValue = (type == "anyOf");
CHECK_OK(AnyAllOfConditionHelper(*c, value));
out = std::move(c);
return ReadFileResult::READ_OK;
}
if (type == "not") {
auto c = cm::make_unique<cmCMakePresetsGraphInternal::NotCondition>();
CHECK_OK(NotConditionHelper(*c, value));
out = std::move(c);
return ReadFileResult::READ_OK;
}
}
return ReadFileResult::INVALID_CONDITION;
}
ReadFileResult SubConditionHelper(
std::unique_ptr<cmCMakePresetsGraph::Condition>& out,
const Json::Value* value)
{
std::unique_ptr<cmCMakePresetsGraph::Condition> ptr;
auto result = ConditionHelper(ptr, value);
if (ptr && ptr->IsNull()) {
return ReadFileResult::INVALID_CONDITION;
}
out = std::move(ptr);
return result;
}
ReadFileResult EnvironmentHelper(cm::optional<std::string>& out,
const Json::Value* value)
{
if (!value || value->isNull()) {
out = cm::nullopt;
return ReadFileResult::READ_OK;
}
if (value->isString()) {
out = value->asString();
return ReadFileResult::READ_OK;
}
return ReadFileResult::INVALID_PRESET;
}
auto const VersionIntHelper = cmJSONIntHelper<ReadFileResult>(
ReadFileResult::READ_OK, ReadFileResult::INVALID_VERSION);
auto const VersionHelper = cmJSONRequiredHelper<int, ReadFileResult>(
ReadFileResult::NO_VERSION, VersionIntHelper);
auto const RootVersionHelper =
cmJSONObjectHelper<int, ReadFileResult>(ReadFileResult::READ_OK,
ReadFileResult::INVALID_ROOT)
.Bind("version"_s, VersionHelper, false);
auto const CMakeVersionUIntHelper = cmJSONUIntHelper<ReadFileResult>(
ReadFileResult::READ_OK, ReadFileResult::INVALID_VERSION);
auto const CMakeVersionHelper =
cmJSONObjectHelper<CMakeVersion, ReadFileResult>(
ReadFileResult::READ_OK, ReadFileResult::INVALID_CMAKE_VERSION, false)
.Bind("major"_s, &CMakeVersion::Major, CMakeVersionUIntHelper, false)
.Bind("minor"_s, &CMakeVersion::Minor, CMakeVersionUIntHelper, false)
.Bind("patch"_s, &CMakeVersion::Patch, CMakeVersionUIntHelper, false);
auto const RootPresetsHelper =
cmJSONObjectHelper<RootPresets, ReadFileResult>(
ReadFileResult::READ_OK, ReadFileResult::INVALID_ROOT, false)
.Bind<int>("version"_s, nullptr, VersionHelper)
.Bind("configurePresets"_s, &RootPresets::ConfigurePresets,
cmCMakePresetsGraphInternal::ConfigurePresetsHelper, false)
.Bind("buildPresets"_s, &RootPresets::BuildPresets,
cmCMakePresetsGraphInternal::BuildPresetsHelper, false)
.Bind("testPresets"_s, &RootPresets::TestPresets,
cmCMakePresetsGraphInternal::TestPresetsHelper, false)
.Bind("cmakeMinimumRequired"_s, &RootPresets::CMakeMinimumRequired,
CMakeVersionHelper, false)
.Bind<std::nullptr_t>(
"vendor"_s, nullptr,
cmCMakePresetsGraphInternal::VendorHelper(ReadFileResult::INVALID_ROOT),
false);
}
namespace cmCMakePresetsGraphInternal {
cmCMakePresetsGraph::ReadFileResult PresetStringHelper(
std::string& out, const Json::Value* value)
{
static auto const helper = cmJSONStringHelper<ReadFileResult>(
ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET);
return helper(out, value);
}
cmCMakePresetsGraph::ReadFileResult PresetVectorStringHelper(
std::vector<std::string>& out, const Json::Value* value)
{
static auto const helper = cmJSONVectorHelper<std::string, ReadFileResult>(
ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET,
cmCMakePresetsGraphInternal::PresetStringHelper);
return helper(out, value);
}
cmCMakePresetsGraph::ReadFileResult PresetBoolHelper(bool& out,
const Json::Value* value)
{
static auto const helper = cmJSONBoolHelper<ReadFileResult>(
ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET);
return helper(out, value);
}
cmCMakePresetsGraph::ReadFileResult PresetOptionalBoolHelper(
cm::optional<bool>& out, const Json::Value* value)
{
static auto const helper = cmJSONOptionalHelper<bool, ReadFileResult>(
ReadFileResult::READ_OK, PresetBoolHelper);
return helper(out, value);
}
cmCMakePresetsGraph::ReadFileResult PresetIntHelper(int& out,
const Json::Value* value)
{
static auto const helper = cmJSONIntHelper<ReadFileResult>(
ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET);
return helper(out, value);
}
cmCMakePresetsGraph::ReadFileResult PresetOptionalIntHelper(
cm::optional<int>& out, const Json::Value* value)
{
static auto const helper = cmJSONOptionalHelper<int, ReadFileResult>(
ReadFileResult::READ_OK, PresetIntHelper);
return helper(out, value);
}
cmCMakePresetsGraph::ReadFileResult PresetVectorIntHelper(
std::vector<int>& out, const Json::Value* value)
{
static auto const helper = cmJSONVectorHelper<int, ReadFileResult>(
ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET, PresetIntHelper);
return helper(out, value);
}
cmJSONHelper<std::nullptr_t, ReadFileResult> VendorHelper(ReadFileResult error)
{
return [error](std::nullptr_t& /*out*/,
const Json::Value* value) -> ReadFileResult {
if (!value) {
return ReadFileResult::READ_OK;
}
if (!value->isObject()) {
return error;
}
return ReadFileResult::READ_OK;
};
}
ReadFileResult PresetConditionHelper(
std::shared_ptr<cmCMakePresetsGraph::Condition>& out,
const Json::Value* value)
{
std::unique_ptr<cmCMakePresetsGraph::Condition> ptr;
auto result = ConditionHelper(ptr, value);
out = std::move(ptr);
return result;
}
ReadFileResult PresetVectorOneOrMoreStringHelper(std::vector<std::string>& out,
const Json::Value* value)
{
out.clear();
if (!value) {
return ReadFileResult::READ_OK;
}
if (value->isString()) {
out.push_back(value->asString());
return ReadFileResult::READ_OK;
}
return PresetVectorStringHelper(out, value);
}
cmCMakePresetsGraph::ReadFileResult EnvironmentMapHelper(
std::map<std::string, cm::optional<std::string>>& out,
const Json::Value* value)
{
static auto const helper =
cmJSONMapHelper<cm::optional<std::string>, ReadFileResult>(
ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET,
EnvironmentHelper);
return helper(out, value);
}
}
cmCMakePresetsGraph::ReadFileResult cmCMakePresetsGraph::ReadJSONFile(
const std::string& filename, RootType rootType, ReadReason readReason,
std::vector<File*>& inProgressFiles, File*& file)
{
for (auto const& f : this->Files) {
if (cmSystemTools::SameFile(filename, f->Filename)) {
file = f.get();
auto fileIt =
std::find(inProgressFiles.begin(), inProgressFiles.end(), file);
if (fileIt != inProgressFiles.end()) {
return cmCMakePresetsGraph::ReadFileResult::CYCLIC_INCLUDE;
}
return cmCMakePresetsGraph::ReadFileResult::READ_OK;
}
}
cmsys::ifstream fin(filename.c_str());
if (!fin) {
return ReadFileResult::FILE_NOT_FOUND;
}
// If there's a BOM, toss it.
cmsys::FStream::ReadBOM(fin);
Json::Value root;
Json::CharReaderBuilder builder;
Json::CharReaderBuilder::strictMode(&builder.settings_);
if (!Json::parseFromStream(builder, fin, &root, nullptr)) {
return ReadFileResult::JSON_PARSE_ERROR;
}
int v = 0;
auto result = RootVersionHelper(v, &root);
if (result != ReadFileResult::READ_OK) {
return result;
}
if (v < MIN_VERSION || v > MAX_VERSION) {
return ReadFileResult::UNRECOGNIZED_VERSION;
}
// Support for build and test presets added in version 2.
if (v < 2 &&
(root.isMember("buildPresets") || root.isMember("testPresets"))) {
return ReadFileResult::BUILD_TEST_PRESETS_UNSUPPORTED;
}
RootPresets presets;
if ((result = RootPresetsHelper(presets, &root)) !=
ReadFileResult::READ_OK) {
return result;
}
unsigned int currentMajor = cmVersion::GetMajorVersion();
unsigned int currentMinor = cmVersion::GetMinorVersion();
unsigned int currentPatch = cmVersion::GetPatchVersion();
auto const& required = presets.CMakeMinimumRequired;
if (required.Major > currentMajor ||
(required.Major == currentMajor &&
(required.Minor > currentMinor ||
(required.Minor == currentMinor &&
(required.Patch > currentPatch))))) {
return ReadFileResult::UNRECOGNIZED_CMAKE_VERSION;
}
auto filePtr = cm::make_unique<File>();
file = filePtr.get();
this->Files.emplace_back(std::move(filePtr));
inProgressFiles.emplace_back(file);
file->Filename = filename;
file->Version = v;
file->ReachableFiles.insert(file);
for (auto& preset : presets.ConfigurePresets) {
preset.OriginFile = file;
if (preset.Name.empty()) {
return ReadFileResult::INVALID_PRESET;
}
PresetPair<ConfigurePreset> presetPair;
presetPair.Unexpanded = preset;
presetPair.Expanded = cm::nullopt;
if (!this->ConfigurePresets
.emplace(std::make_pair(preset.Name, presetPair))
.second) {
return ReadFileResult::DUPLICATE_PRESETS;
}
// Support for installDir presets added in version 3.
if (v < 3 && !preset.InstallDir.empty()) {
return ReadFileResult::INSTALL_PREFIX_UNSUPPORTED;
}
// Support for conditions added in version 3.
if (v < 3 && preset.ConditionEvaluator) {
return ReadFileResult::CONDITION_UNSUPPORTED;
}
// Support for toolchainFile presets added in version 3.
if (v < 3 && !preset.ToolchainFile.empty()) {
return ReadFileResult::TOOLCHAIN_FILE_UNSUPPORTED;
}
this->ConfigurePresetOrder.push_back(preset.Name);
}
for (auto& preset : presets.BuildPresets) {
preset.OriginFile = file;
if (preset.Name.empty()) {
return ReadFileResult::INVALID_PRESET;
}
PresetPair<BuildPreset> presetPair;
presetPair.Unexpanded = preset;
presetPair.Expanded = cm::nullopt;
if (!this->BuildPresets.emplace(preset.Name, presetPair).second) {
return ReadFileResult::DUPLICATE_PRESETS;
}
// Support for conditions added in version 3.
if (v < 3 && preset.ConditionEvaluator) {
return ReadFileResult::CONDITION_UNSUPPORTED;
}
this->BuildPresetOrder.push_back(preset.Name);
}
for (auto& preset : presets.TestPresets) {
preset.OriginFile = file;
if (preset.Name.empty()) {
return ReadFileResult::INVALID_PRESET;
}
PresetPair<TestPreset> presetPair;
presetPair.Unexpanded = preset;
presetPair.Expanded = cm::nullopt;
if (!this->TestPresets.emplace(preset.Name, presetPair).second) {
return ReadFileResult::DUPLICATE_PRESETS;
}
// Support for conditions added in version 3.
if (v < 3 && preset.ConditionEvaluator) {
return ReadFileResult::CONDITION_UNSUPPORTED;
}
this->TestPresetOrder.push_back(preset.Name);
}
auto const includeFile = [this, &inProgressFiles, file](
const std::string& include, RootType rootType2,
ReadReason readReason2) -> ReadFileResult {
ReadFileResult r;
File* includedFile;
if ((r = this->ReadJSONFile(include, rootType2, readReason2,
inProgressFiles, includedFile)) !=
ReadFileResult::READ_OK) {
return r;
}
file->ReachableFiles.insert(includedFile->ReachableFiles.begin(),
includedFile->ReachableFiles.end());
return ReadFileResult::READ_OK;
};
if (rootType == RootType::User && readReason == ReadReason::Root) {
auto cmakePresetsFilename = GetFilename(this->SourceDir);
if (cmSystemTools::FileExists(cmakePresetsFilename)) {
if ((result = includeFile(cmakePresetsFilename, RootType::Project,
ReadReason::Root)) !=
ReadFileResult::READ_OK) {
return result;
}
}
}
inProgressFiles.pop_back();
return ReadFileResult::READ_OK;
}