1 /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
2 file Copyright.txt or https://cmake.org/licensing for details. */
7 #include <unordered_set>
12 #include <cm/optional>
13 #include <cmext/string_view>
15 #include <cm3p/json/reader.h>
16 #include <cm3p/json/value.h>
18 #include "cmsys/FStream.hxx"
20 #include "cmCMakePresetsGraph.h"
21 #include "cmCMakePresetsGraphInternal.h"
22 #include "cmJSONHelpers.h"
23 #include "cmStringAlgorithms.h"
24 #include "cmSystemTools.h"
25 #include "cmVersion.h"
28 using ReadFileResult = cmCMakePresetsGraph::ReadFileResult;
29 using CacheVariable = cmCMakePresetsGraph::CacheVariable;
30 using ConfigurePreset = cmCMakePresetsGraph::ConfigurePreset;
31 using BuildPreset = cmCMakePresetsGraph::BuildPreset;
32 using TestPreset = cmCMakePresetsGraph::TestPreset;
33 using ArchToolsetStrategy = cmCMakePresetsGraph::ArchToolsetStrategy;
34 using JSONHelperBuilder = cmJSONHelperBuilder<ReadFileResult>;
36 constexpr int MIN_VERSION = 1;
37 constexpr int MAX_VERSION = 5;
41 unsigned int Major = 0;
42 unsigned int Minor = 0;
43 unsigned int Patch = 0;
48 CMakeVersion CMakeMinimumRequired;
49 std::vector<cmCMakePresetsGraph::ConfigurePreset> ConfigurePresets;
50 std::vector<cmCMakePresetsGraph::BuildPreset> BuildPresets;
51 std::vector<cmCMakePresetsGraph::TestPreset> TestPresets;
52 std::vector<std::string> Include;
55 std::unique_ptr<cmCMakePresetsGraphInternal::NotCondition> InvertCondition(
56 std::unique_ptr<cmCMakePresetsGraph::Condition> condition)
58 auto retval = cm::make_unique<cmCMakePresetsGraphInternal::NotCondition>();
59 retval->SubCondition = std::move(condition);
63 auto const ConditionStringHelper = JSONHelperBuilder::String(
64 ReadFileResult::READ_OK, ReadFileResult::INVALID_CONDITION);
66 auto const ConditionBoolHelper = JSONHelperBuilder::Bool(
67 ReadFileResult::READ_OK, ReadFileResult::INVALID_CONDITION);
69 auto const ConditionStringListHelper = JSONHelperBuilder::Vector<std::string>(
70 ReadFileResult::READ_OK, ReadFileResult::INVALID_CONDITION,
71 ConditionStringHelper);
73 auto const ConstConditionHelper =
74 JSONHelperBuilder::Object<cmCMakePresetsGraphInternal::ConstCondition>(
75 ReadFileResult::READ_OK, ReadFileResult::INVALID_CONDITION, false)
76 .Bind<std::string>("type"_s, nullptr, ConditionStringHelper, true)
77 .Bind("value"_s, &cmCMakePresetsGraphInternal::ConstCondition::Value,
78 ConditionBoolHelper, true);
80 auto const EqualsConditionHelper =
81 JSONHelperBuilder::Object<cmCMakePresetsGraphInternal::EqualsCondition>(
82 ReadFileResult::READ_OK, ReadFileResult::INVALID_CONDITION, false)
83 .Bind<std::string>("type"_s, nullptr, ConditionStringHelper, true)
84 .Bind("lhs"_s, &cmCMakePresetsGraphInternal::EqualsCondition::Lhs,
85 ConditionStringHelper, true)
86 .Bind("rhs"_s, &cmCMakePresetsGraphInternal::EqualsCondition::Rhs,
87 ConditionStringHelper, true);
89 auto const InListConditionHelper =
90 JSONHelperBuilder::Object<cmCMakePresetsGraphInternal::InListCondition>(
91 ReadFileResult::READ_OK, ReadFileResult::INVALID_CONDITION, false)
92 .Bind<std::string>("type"_s, nullptr, ConditionStringHelper, true)
93 .Bind("string"_s, &cmCMakePresetsGraphInternal::InListCondition::String,
94 ConditionStringHelper, true)
95 .Bind("list"_s, &cmCMakePresetsGraphInternal::InListCondition::List,
96 ConditionStringListHelper, true);
98 auto const MatchesConditionHelper =
99 JSONHelperBuilder::Object<cmCMakePresetsGraphInternal::MatchesCondition>(
100 ReadFileResult::READ_OK, ReadFileResult::INVALID_CONDITION, false)
101 .Bind<std::string>("type"_s, nullptr, ConditionStringHelper, true)
102 .Bind("string"_s, &cmCMakePresetsGraphInternal::MatchesCondition::String,
103 ConditionStringHelper, true)
104 .Bind("regex"_s, &cmCMakePresetsGraphInternal::MatchesCondition::Regex,
105 ConditionStringHelper, true);
107 ReadFileResult SubConditionHelper(
108 std::unique_ptr<cmCMakePresetsGraph::Condition>& out,
109 const Json::Value* value);
111 auto const ListConditionVectorHelper =
112 JSONHelperBuilder::Vector<std::unique_ptr<cmCMakePresetsGraph::Condition>>(
113 ReadFileResult::READ_OK, ReadFileResult::INVALID_CONDITION,
115 auto const AnyAllOfConditionHelper =
116 JSONHelperBuilder::Object<cmCMakePresetsGraphInternal::AnyAllOfCondition>(
117 ReadFileResult::READ_OK, ReadFileResult::INVALID_CONDITION, false)
118 .Bind<std::string>("type"_s, nullptr, ConditionStringHelper, true)
119 .Bind("conditions"_s,
120 &cmCMakePresetsGraphInternal::AnyAllOfCondition::Conditions,
121 ListConditionVectorHelper);
123 auto const NotConditionHelper =
124 JSONHelperBuilder::Object<cmCMakePresetsGraphInternal::NotCondition>(
125 ReadFileResult::READ_OK, ReadFileResult::INVALID_CONDITION, false)
126 .Bind<std::string>("type"_s, nullptr, ConditionStringHelper, true)
128 &cmCMakePresetsGraphInternal::NotCondition::SubCondition,
131 ReadFileResult ConditionHelper(
132 std::unique_ptr<cmCMakePresetsGraph::Condition>& out,
133 const Json::Value* value)
137 return ReadFileResult::READ_OK;
140 if (value->isBool()) {
141 auto c = cm::make_unique<cmCMakePresetsGraphInternal::ConstCondition>();
142 c->Value = value->asBool();
144 return ReadFileResult::READ_OK;
147 if (value->isNull()) {
148 out = cm::make_unique<cmCMakePresetsGraphInternal::NullCondition>();
149 return ReadFileResult::READ_OK;
152 if (value->isObject()) {
153 if (!value->isMember("type")) {
154 return ReadFileResult::INVALID_CONDITION;
157 if (!(*value)["type"].isString()) {
158 return ReadFileResult::INVALID_CONDITION;
160 auto type = (*value)["type"].asString();
162 if (type == "const") {
163 auto c = cm::make_unique<cmCMakePresetsGraphInternal::ConstCondition>();
164 CHECK_OK(ConstConditionHelper(*c, value));
166 return ReadFileResult::READ_OK;
169 if (type == "equals" || type == "notEquals") {
170 auto c = cm::make_unique<cmCMakePresetsGraphInternal::EqualsCondition>();
171 CHECK_OK(EqualsConditionHelper(*c, value));
173 if (type == "notEquals") {
174 out = InvertCondition(std::move(out));
176 return ReadFileResult::READ_OK;
179 if (type == "inList" || type == "notInList") {
180 auto c = cm::make_unique<cmCMakePresetsGraphInternal::InListCondition>();
181 CHECK_OK(InListConditionHelper(*c, value));
183 if (type == "notInList") {
184 out = InvertCondition(std::move(out));
186 return ReadFileResult::READ_OK;
189 if (type == "matches" || type == "notMatches") {
191 cm::make_unique<cmCMakePresetsGraphInternal::MatchesCondition>();
192 CHECK_OK(MatchesConditionHelper(*c, value));
194 if (type == "notMatches") {
195 out = InvertCondition(std::move(out));
197 return ReadFileResult::READ_OK;
200 if (type == "anyOf" || type == "allOf") {
202 cm::make_unique<cmCMakePresetsGraphInternal::AnyAllOfCondition>();
203 c->StopValue = (type == "anyOf");
204 CHECK_OK(AnyAllOfConditionHelper(*c, value));
206 return ReadFileResult::READ_OK;
210 auto c = cm::make_unique<cmCMakePresetsGraphInternal::NotCondition>();
211 CHECK_OK(NotConditionHelper(*c, value));
213 return ReadFileResult::READ_OK;
217 return ReadFileResult::INVALID_CONDITION;
220 ReadFileResult SubConditionHelper(
221 std::unique_ptr<cmCMakePresetsGraph::Condition>& out,
222 const Json::Value* value)
224 std::unique_ptr<cmCMakePresetsGraph::Condition> ptr;
225 auto result = ConditionHelper(ptr, value);
226 if (ptr && ptr->IsNull()) {
227 return ReadFileResult::INVALID_CONDITION;
229 out = std::move(ptr);
233 ReadFileResult EnvironmentHelper(cm::optional<std::string>& out,
234 const Json::Value* value)
236 if (!value || value->isNull()) {
238 return ReadFileResult::READ_OK;
240 if (value->isString()) {
241 out = value->asString();
242 return ReadFileResult::READ_OK;
244 return ReadFileResult::INVALID_PRESET;
247 auto const VersionIntHelper = JSONHelperBuilder::Int(
248 ReadFileResult::READ_OK, ReadFileResult::INVALID_VERSION);
250 auto const VersionHelper = JSONHelperBuilder::Required<int>(
251 ReadFileResult::NO_VERSION, VersionIntHelper);
253 auto const RootVersionHelper =
254 JSONHelperBuilder::Object<int>(ReadFileResult::READ_OK,
255 ReadFileResult::INVALID_ROOT)
256 .Bind("version"_s, VersionHelper, false);
258 auto const CMakeVersionUIntHelper = JSONHelperBuilder::UInt(
259 ReadFileResult::READ_OK, ReadFileResult::INVALID_VERSION);
261 auto const CMakeVersionHelper =
262 JSONHelperBuilder::Object<CMakeVersion>(
263 ReadFileResult::READ_OK, ReadFileResult::INVALID_CMAKE_VERSION, false)
264 .Bind("major"_s, &CMakeVersion::Major, CMakeVersionUIntHelper, false)
265 .Bind("minor"_s, &CMakeVersion::Minor, CMakeVersionUIntHelper, false)
266 .Bind("patch"_s, &CMakeVersion::Patch, CMakeVersionUIntHelper, false);
268 auto const IncludeHelper = JSONHelperBuilder::String(
269 ReadFileResult::READ_OK, ReadFileResult::INVALID_INCLUDE);
271 auto const IncludeVectorHelper = JSONHelperBuilder::Vector<std::string>(
272 ReadFileResult::READ_OK, ReadFileResult::INVALID_INCLUDE, IncludeHelper);
274 auto const RootPresetsHelper =
275 JSONHelperBuilder::Object<RootPresets>(ReadFileResult::READ_OK,
276 ReadFileResult::INVALID_ROOT, false)
277 .Bind<int>("version"_s, nullptr, VersionHelper)
278 .Bind("configurePresets"_s, &RootPresets::ConfigurePresets,
279 cmCMakePresetsGraphInternal::ConfigurePresetsHelper, false)
280 .Bind("buildPresets"_s, &RootPresets::BuildPresets,
281 cmCMakePresetsGraphInternal::BuildPresetsHelper, false)
282 .Bind("testPresets"_s, &RootPresets::TestPresets,
283 cmCMakePresetsGraphInternal::TestPresetsHelper, false)
284 .Bind("cmakeMinimumRequired"_s, &RootPresets::CMakeMinimumRequired,
285 CMakeVersionHelper, false)
286 .Bind("include"_s, &RootPresets::Include, IncludeVectorHelper, false)
287 .Bind<std::nullptr_t>(
289 cmCMakePresetsGraphInternal::VendorHelper(ReadFileResult::INVALID_ROOT),
293 namespace cmCMakePresetsGraphInternal {
294 cmCMakePresetsGraph::ReadFileResult PresetStringHelper(
295 std::string& out, const Json::Value* value)
297 static auto const helper = JSONHelperBuilder::String(
298 ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET);
300 return helper(out, value);
303 cmCMakePresetsGraph::ReadFileResult PresetVectorStringHelper(
304 std::vector<std::string>& out, const Json::Value* value)
306 static auto const helper = JSONHelperBuilder::Vector<std::string>(
307 ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET,
308 cmCMakePresetsGraphInternal::PresetStringHelper);
310 return helper(out, value);
313 cmCMakePresetsGraph::ReadFileResult PresetBoolHelper(bool& out,
314 const Json::Value* value)
316 static auto const helper = JSONHelperBuilder::Bool(
317 ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET);
319 return helper(out, value);
322 cmCMakePresetsGraph::ReadFileResult PresetOptionalBoolHelper(
323 cm::optional<bool>& out, const Json::Value* value)
325 static auto const helper = JSONHelperBuilder::Optional<bool>(
326 ReadFileResult::READ_OK, PresetBoolHelper);
328 return helper(out, value);
331 cmCMakePresetsGraph::ReadFileResult PresetIntHelper(int& out,
332 const Json::Value* value)
334 static auto const helper = JSONHelperBuilder::Int(
335 ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET);
337 return helper(out, value);
340 cmCMakePresetsGraph::ReadFileResult PresetOptionalIntHelper(
341 cm::optional<int>& out, const Json::Value* value)
343 static auto const helper =
344 JSONHelperBuilder::Optional<int>(ReadFileResult::READ_OK, PresetIntHelper);
346 return helper(out, value);
349 cmCMakePresetsGraph::ReadFileResult PresetVectorIntHelper(
350 std::vector<int>& out, const Json::Value* value)
352 static auto const helper = JSONHelperBuilder::Vector<int>(
353 ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET, PresetIntHelper);
355 return helper(out, value);
358 cmJSONHelper<std::nullptr_t, ReadFileResult> VendorHelper(ReadFileResult error)
360 return [error](std::nullptr_t& /*out*/,
361 const Json::Value* value) -> ReadFileResult {
363 return ReadFileResult::READ_OK;
366 if (!value->isObject()) {
370 return ReadFileResult::READ_OK;
374 ReadFileResult PresetConditionHelper(
375 std::shared_ptr<cmCMakePresetsGraph::Condition>& out,
376 const Json::Value* value)
378 std::unique_ptr<cmCMakePresetsGraph::Condition> ptr;
379 auto result = ConditionHelper(ptr, value);
380 out = std::move(ptr);
384 ReadFileResult PresetVectorOneOrMoreStringHelper(std::vector<std::string>& out,
385 const Json::Value* value)
389 return ReadFileResult::READ_OK;
392 if (value->isString()) {
393 out.push_back(value->asString());
394 return ReadFileResult::READ_OK;
397 return PresetVectorStringHelper(out, value);
400 cmCMakePresetsGraph::ReadFileResult EnvironmentMapHelper(
401 std::map<std::string, cm::optional<std::string>>& out,
402 const Json::Value* value)
404 static auto const helper = JSONHelperBuilder::Map<cm::optional<std::string>>(
405 ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET,
408 return helper(out, value);
412 cmCMakePresetsGraph::ReadFileResult cmCMakePresetsGraph::ReadJSONFile(
413 const std::string& filename, RootType rootType, ReadReason readReason,
414 std::vector<File*>& inProgressFiles, File*& file)
416 ReadFileResult result;
418 for (auto const& f : this->Files) {
419 if (cmSystemTools::SameFile(filename, f->Filename)) {
422 std::find(inProgressFiles.begin(), inProgressFiles.end(), file);
423 if (fileIt != inProgressFiles.end()) {
424 return cmCMakePresetsGraph::ReadFileResult::CYCLIC_INCLUDE;
427 return cmCMakePresetsGraph::ReadFileResult::READ_OK;
431 cmsys::ifstream fin(filename.c_str());
433 return ReadFileResult::FILE_NOT_FOUND;
435 // If there's a BOM, toss it.
436 cmsys::FStream::ReadBOM(fin);
439 Json::CharReaderBuilder builder;
440 Json::CharReaderBuilder::strictMode(&builder.settings_);
441 if (!Json::parseFromStream(builder, fin, &root, nullptr)) {
442 return ReadFileResult::JSON_PARSE_ERROR;
446 if ((result = RootVersionHelper(v, &root)) != ReadFileResult::READ_OK) {
449 if (v < MIN_VERSION || v > MAX_VERSION) {
450 return ReadFileResult::UNRECOGNIZED_VERSION;
453 // Support for build and test presets added in version 2.
455 (root.isMember("buildPresets") || root.isMember("testPresets"))) {
456 return ReadFileResult::BUILD_TEST_PRESETS_UNSUPPORTED;
459 // Support for include added in version 4.
460 if (v < 4 && root.isMember("include")) {
461 return ReadFileResult::INCLUDE_UNSUPPORTED;
465 if ((result = RootPresetsHelper(presets, &root)) !=
466 ReadFileResult::READ_OK) {
470 unsigned int currentMajor = cmVersion::GetMajorVersion();
471 unsigned int currentMinor = cmVersion::GetMinorVersion();
472 unsigned int currentPatch = cmVersion::GetPatchVersion();
473 auto const& required = presets.CMakeMinimumRequired;
474 if (required.Major > currentMajor ||
475 (required.Major == currentMajor &&
476 (required.Minor > currentMinor ||
477 (required.Minor == currentMinor &&
478 (required.Patch > currentPatch))))) {
479 return ReadFileResult::UNRECOGNIZED_CMAKE_VERSION;
482 auto filePtr = cm::make_unique<File>();
483 file = filePtr.get();
484 this->Files.emplace_back(std::move(filePtr));
485 inProgressFiles.emplace_back(file);
486 file->Filename = filename;
488 file->ReachableFiles.insert(file);
490 for (auto& preset : presets.ConfigurePresets) {
491 preset.OriginFile = file;
492 if (preset.Name.empty()) {
493 return ReadFileResult::INVALID_PRESET;
496 PresetPair<ConfigurePreset> presetPair;
497 presetPair.Unexpanded = preset;
498 presetPair.Expanded = cm::nullopt;
499 if (!this->ConfigurePresets
500 .emplace(std::make_pair(preset.Name, presetPair))
502 return ReadFileResult::DUPLICATE_PRESETS;
505 // Support for installDir presets added in version 3.
506 if (v < 3 && !preset.InstallDir.empty()) {
507 return ReadFileResult::INSTALL_PREFIX_UNSUPPORTED;
510 // Support for conditions added in version 3.
511 if (v < 3 && preset.ConditionEvaluator) {
512 return ReadFileResult::CONDITION_UNSUPPORTED;
515 // Support for toolchainFile presets added in version 3.
516 if (v < 3 && !preset.ToolchainFile.empty()) {
517 return ReadFileResult::TOOLCHAIN_FILE_UNSUPPORTED;
520 this->ConfigurePresetOrder.push_back(preset.Name);
523 for (auto& preset : presets.BuildPresets) {
524 preset.OriginFile = file;
525 if (preset.Name.empty()) {
526 return ReadFileResult::INVALID_PRESET;
529 PresetPair<BuildPreset> presetPair;
530 presetPair.Unexpanded = preset;
531 presetPair.Expanded = cm::nullopt;
532 if (!this->BuildPresets.emplace(preset.Name, presetPair).second) {
533 return ReadFileResult::DUPLICATE_PRESETS;
536 // Support for conditions added in version 3.
537 if (v < 3 && preset.ConditionEvaluator) {
538 return ReadFileResult::CONDITION_UNSUPPORTED;
541 this->BuildPresetOrder.push_back(preset.Name);
544 for (auto& preset : presets.TestPresets) {
545 preset.OriginFile = file;
546 if (preset.Name.empty()) {
547 return ReadFileResult::INVALID_PRESET;
550 PresetPair<TestPreset> presetPair;
551 presetPair.Unexpanded = preset;
552 presetPair.Expanded = cm::nullopt;
553 if (!this->TestPresets.emplace(preset.Name, presetPair).second) {
554 return ReadFileResult::DUPLICATE_PRESETS;
557 // Support for conditions added in version 3.
558 if (v < 3 && preset.ConditionEvaluator) {
559 return ReadFileResult::CONDITION_UNSUPPORTED;
562 // Support for TestOutputTruncation added in version 5.
563 if (v < 5 && preset.Output && preset.Output->TestOutputTruncation) {
564 return ReadFileResult::TEST_OUTPUT_TRUNCATION_UNSUPPORTED;
567 this->TestPresetOrder.push_back(preset.Name);
570 auto const includeFile = [this, &inProgressFiles, file](
571 const std::string& include, RootType rootType2,
572 ReadReason readReason2) -> ReadFileResult {
575 if ((r = this->ReadJSONFile(include, rootType2, readReason2,
576 inProgressFiles, includedFile)) !=
577 ReadFileResult::READ_OK) {
581 file->ReachableFiles.insert(includedFile->ReachableFiles.begin(),
582 includedFile->ReachableFiles.end());
583 return ReadFileResult::READ_OK;
586 for (auto include : presets.Include) {
587 if (!cmSystemTools::FileIsFullPath(include)) {
588 auto directory = cmSystemTools::GetFilenamePath(filename);
589 include = cmStrCat(directory, '/', include);
592 if ((result = includeFile(include, rootType, ReadReason::Included)) !=
593 ReadFileResult::READ_OK) {
598 if (rootType == RootType::User && readReason == ReadReason::Root) {
599 auto cmakePresetsFilename = GetFilename(this->SourceDir);
600 if (cmSystemTools::FileExists(cmakePresetsFilename)) {
601 if ((result = includeFile(cmakePresetsFilename, RootType::Project,
602 ReadReason::Root)) !=
603 ReadFileResult::READ_OK) {
609 inProgressFiles.pop_back();
610 return ReadFileResult::READ_OK;