/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying file Copyright.txt or https://cmake.org/licensing for details. */ #include #include #include #include #include #include #include #include #include #include #include #include #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 PackagePreset = cmCMakePresetsGraph::PackagePreset; using WorkflowPreset = cmCMakePresetsGraph::WorkflowPreset; using ArchToolsetStrategy = cmCMakePresetsGraph::ArchToolsetStrategy; using JSONHelperBuilder = cmJSONHelperBuilder; constexpr int MIN_VERSION = 1; constexpr int MAX_VERSION = 6; struct CMakeVersion { unsigned int Major = 0; unsigned int Minor = 0; unsigned int Patch = 0; }; struct RootPresets { CMakeVersion CMakeMinimumRequired; std::vector ConfigurePresets; std::vector BuildPresets; std::vector TestPresets; std::vector PackagePresets; std::vector WorkflowPresets; std::vector Include; }; std::unique_ptr InvertCondition( std::unique_ptr condition) { auto retval = cm::make_unique(); retval->SubCondition = std::move(condition); return retval; } auto const ConditionStringHelper = JSONHelperBuilder::String( ReadFileResult::READ_OK, ReadFileResult::INVALID_CONDITION); auto const ConditionBoolHelper = JSONHelperBuilder::Bool( ReadFileResult::READ_OK, ReadFileResult::INVALID_CONDITION); auto const ConditionStringListHelper = JSONHelperBuilder::Vector( ReadFileResult::READ_OK, ReadFileResult::INVALID_CONDITION, ConditionStringHelper); auto const ConstConditionHelper = JSONHelperBuilder::Object( ReadFileResult::READ_OK, ReadFileResult::INVALID_CONDITION, false) .Bind("type"_s, nullptr, ConditionStringHelper, true) .Bind("value"_s, &cmCMakePresetsGraphInternal::ConstCondition::Value, ConditionBoolHelper, true); auto const EqualsConditionHelper = JSONHelperBuilder::Object( ReadFileResult::READ_OK, ReadFileResult::INVALID_CONDITION, false) .Bind("type"_s, nullptr, ConditionStringHelper, true) .Bind("lhs"_s, &cmCMakePresetsGraphInternal::EqualsCondition::Lhs, ConditionStringHelper, true) .Bind("rhs"_s, &cmCMakePresetsGraphInternal::EqualsCondition::Rhs, ConditionStringHelper, true); auto const InListConditionHelper = JSONHelperBuilder::Object( ReadFileResult::READ_OK, ReadFileResult::INVALID_CONDITION, false) .Bind("type"_s, nullptr, ConditionStringHelper, true) .Bind("string"_s, &cmCMakePresetsGraphInternal::InListCondition::String, ConditionStringHelper, true) .Bind("list"_s, &cmCMakePresetsGraphInternal::InListCondition::List, ConditionStringListHelper, true); auto const MatchesConditionHelper = JSONHelperBuilder::Object( ReadFileResult::READ_OK, ReadFileResult::INVALID_CONDITION, false) .Bind("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& out, const Json::Value* value); auto const ListConditionVectorHelper = JSONHelperBuilder::Vector>( ReadFileResult::READ_OK, ReadFileResult::INVALID_CONDITION, SubConditionHelper); auto const AnyAllOfConditionHelper = JSONHelperBuilder::Object( ReadFileResult::READ_OK, ReadFileResult::INVALID_CONDITION, false) .Bind("type"_s, nullptr, ConditionStringHelper, true) .Bind("conditions"_s, &cmCMakePresetsGraphInternal::AnyAllOfCondition::Conditions, ListConditionVectorHelper); auto const NotConditionHelper = JSONHelperBuilder::Object( ReadFileResult::READ_OK, ReadFileResult::INVALID_CONDITION, false) .Bind("type"_s, nullptr, ConditionStringHelper, true) .Bind("condition"_s, &cmCMakePresetsGraphInternal::NotCondition::SubCondition, SubConditionHelper); ReadFileResult ConditionHelper( std::unique_ptr& out, const Json::Value* value) { if (!value) { out.reset(); return ReadFileResult::READ_OK; } if (value->isBool()) { auto c = cm::make_unique(); c->Value = value->asBool(); out = std::move(c); return ReadFileResult::READ_OK; } if (value->isNull()) { out = cm::make_unique(); 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(); CHECK_OK(ConstConditionHelper(*c, value)); out = std::move(c); return ReadFileResult::READ_OK; } if (type == "equals" || type == "notEquals") { auto c = cm::make_unique(); 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(); 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(); 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(); 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(); CHECK_OK(NotConditionHelper(*c, value)); out = std::move(c); return ReadFileResult::READ_OK; } } return ReadFileResult::INVALID_CONDITION; } ReadFileResult SubConditionHelper( std::unique_ptr& out, const Json::Value* value) { std::unique_ptr ptr; auto result = ConditionHelper(ptr, value); if (ptr && ptr->IsNull()) { return ReadFileResult::INVALID_CONDITION; } out = std::move(ptr); return result; } ReadFileResult EnvironmentHelper(cm::optional& 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 = JSONHelperBuilder::Int( ReadFileResult::READ_OK, ReadFileResult::INVALID_VERSION); auto const VersionHelper = JSONHelperBuilder::Required( ReadFileResult::NO_VERSION, VersionIntHelper); auto const RootVersionHelper = JSONHelperBuilder::Object(ReadFileResult::READ_OK, ReadFileResult::INVALID_ROOT) .Bind("version"_s, VersionHelper, false); auto const CMakeVersionUIntHelper = JSONHelperBuilder::UInt( ReadFileResult::READ_OK, ReadFileResult::INVALID_VERSION); auto const CMakeVersionHelper = JSONHelperBuilder::Object( 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 IncludeHelper = JSONHelperBuilder::String( ReadFileResult::READ_OK, ReadFileResult::INVALID_INCLUDE); auto const IncludeVectorHelper = JSONHelperBuilder::Vector( ReadFileResult::READ_OK, ReadFileResult::INVALID_INCLUDE, IncludeHelper); auto const RootPresetsHelper = JSONHelperBuilder::Object(ReadFileResult::READ_OK, ReadFileResult::INVALID_ROOT, false) .Bind("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("packagePresets"_s, &RootPresets::PackagePresets, cmCMakePresetsGraphInternal::PackagePresetsHelper, false) .Bind("workflowPresets"_s, &RootPresets::WorkflowPresets, cmCMakePresetsGraphInternal::WorkflowPresetsHelper, false) .Bind("cmakeMinimumRequired"_s, &RootPresets::CMakeMinimumRequired, CMakeVersionHelper, false) .Bind("include"_s, &RootPresets::Include, IncludeVectorHelper, false) .Bind( "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 = JSONHelperBuilder::String( ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET); return helper(out, value); } cmCMakePresetsGraph::ReadFileResult PresetVectorStringHelper( std::vector& out, const Json::Value* value) { static auto const helper = JSONHelperBuilder::Vector( 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 = JSONHelperBuilder::Bool( ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET); return helper(out, value); } cmCMakePresetsGraph::ReadFileResult PresetOptionalBoolHelper( cm::optional& out, const Json::Value* value) { static auto const helper = JSONHelperBuilder::Optional( ReadFileResult::READ_OK, PresetBoolHelper); return helper(out, value); } cmCMakePresetsGraph::ReadFileResult PresetIntHelper(int& out, const Json::Value* value) { static auto const helper = JSONHelperBuilder::Int( ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET); return helper(out, value); } cmCMakePresetsGraph::ReadFileResult PresetOptionalIntHelper( cm::optional& out, const Json::Value* value) { static auto const helper = JSONHelperBuilder::Optional(ReadFileResult::READ_OK, PresetIntHelper); return helper(out, value); } cmCMakePresetsGraph::ReadFileResult PresetVectorIntHelper( std::vector& out, const Json::Value* value) { static auto const helper = JSONHelperBuilder::Vector( ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET, PresetIntHelper); return helper(out, value); } cmJSONHelper 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& out, const Json::Value* value) { std::unique_ptr ptr; auto result = ConditionHelper(ptr, value); out = std::move(ptr); return result; } ReadFileResult PresetVectorOneOrMoreStringHelper(std::vector& 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>& out, const Json::Value* value) { static auto const helper = JSONHelperBuilder::Map>( 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& inProgressFiles, File*& file, std::string& errMsg) { ReadFileResult result; 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) { errMsg = cmStrCat(filename, ": Failed to read file\n", errMsg); 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, &errMsg)) { errMsg = cmStrCat(filename, ":\n", errMsg); return ReadFileResult::JSON_PARSE_ERROR; } int v = 0; if ((result = RootVersionHelper(v, &root)) != 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; } // Support for package presets added in version 6. if (v < 6 && root.isMember("packagePresets")) { return ReadFileResult::PACKAGE_PRESETS_UNSUPPORTED; } // Support for workflow presets added in version 6. if (v < 6 && root.isMember("workflowPresets")) { return ReadFileResult::WORKFLOW_PRESETS_UNSUPPORTED; } // Support for include added in version 4. if (v < 4 && root.isMember("include")) { return ReadFileResult::INCLUDE_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 = 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()) { errMsg += R"(\n\t)"; errMsg += filename; return ReadFileResult::INVALID_PRESET; } PresetPair 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()) { errMsg += R"(\n\t)"; errMsg += filename; return ReadFileResult::INVALID_PRESET; } PresetPair 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 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; } // Support for TestOutputTruncation added in version 5. if (v < 5 && preset.Output && preset.Output->TestOutputTruncation) { return ReadFileResult::TEST_OUTPUT_TRUNCATION_UNSUPPORTED; } // Support for outputJUnitFile added in version 6. if (v < 6 && preset.Output && !preset.Output->OutputJUnitFile.empty()) { return ReadFileResult::CTEST_JUNIT_UNSUPPORTED; } this->TestPresetOrder.push_back(preset.Name); } for (auto& preset : presets.PackagePresets) { preset.OriginFile = file; if (preset.Name.empty()) { return ReadFileResult::INVALID_PRESET; } PresetPair presetPair; presetPair.Unexpanded = preset; presetPair.Expanded = cm::nullopt; if (!this->PackagePresets.emplace(preset.Name, presetPair).second) { return ReadFileResult::DUPLICATE_PRESETS; } // Support for conditions added in version 3, but this requires version 5 // already, so no action needed. this->PackagePresetOrder.push_back(preset.Name); } for (auto& preset : presets.WorkflowPresets) { preset.OriginFile = file; if (preset.Name.empty()) { return ReadFileResult::INVALID_PRESET; } PresetPair presetPair; presetPair.Unexpanded = preset; presetPair.Expanded = cm::nullopt; if (!this->WorkflowPresets.emplace(preset.Name, presetPair).second) { return ReadFileResult::DUPLICATE_PRESETS; } // Support for conditions added in version 3, but this requires version 6 // already, so no action needed. this->WorkflowPresetOrder.push_back(preset.Name); } auto const includeFile = [this, &inProgressFiles, file]( const std::string& include, RootType rootType2, ReadReason readReason2, std::string& FailureMessage) -> ReadFileResult { ReadFileResult r; File* includedFile; if ((r = this->ReadJSONFile(include, rootType2, readReason2, inProgressFiles, includedFile, FailureMessage)) != ReadFileResult::READ_OK) { return r; } file->ReachableFiles.insert(includedFile->ReachableFiles.begin(), includedFile->ReachableFiles.end()); return ReadFileResult::READ_OK; }; for (auto include : presets.Include) { if (!cmSystemTools::FileIsFullPath(include)) { auto directory = cmSystemTools::GetFilenamePath(filename); include = cmStrCat(directory, '/', include); } if ((result = includeFile(include, rootType, ReadReason::Included, errMsg)) != ReadFileResult::READ_OK) { return result; } } if (rootType == RootType::User && readReason == ReadReason::Root) { auto cmakePresetsFilename = GetFilename(this->SourceDir); if (cmSystemTools::FileExists(cmakePresetsFilename)) { if ((result = includeFile(cmakePresetsFilename, RootType::Project, ReadReason::Root, errMsg)) != ReadFileResult::READ_OK) { return result; } } } inProgressFiles.pop_back(); return ReadFileResult::READ_OK; }