| /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying | |
| file LICENSE.rst or https://cmake.org/licensing for details. */ | |
| #include "cmInstrumentationCommand.h" | |
| #include <algorithm> | |
| #include <cstdlib> | |
| #include <functional> | |
| #include <set> | |
| #include <sstream> | |
| #include <cmext/string_view> | |
| #include <cm3p/json/value.h> | |
| #include "cmsys/String.h" | |
| #include "cmArgumentParser.h" | |
| #include "cmArgumentParserTypes.h" | |
| #include "cmExecutionStatus.h" | |
| #include "cmInstrumentation.h" | |
| #include "cmInstrumentationQuery.h" | |
| #include "cmJSONState.h" | |
| #include "cmList.h" | |
| #include "cmMakefile.h" | |
| #include "cmMessageType.h" | |
| #include "cmStringAlgorithms.h" | |
| #include "cmValue.h" | |
| #include "cmake.h" | |
| namespace { | |
| using Version = cmInstrumentationQuery::Version; | |
| bool validateVersion(std::string const& key, std::string const& versionString, | |
| int& version, cmExecutionStatus& status) | |
| { | |
| if (!std::all_of(versionString.begin(), versionString.end(), | |
| cmsysString_isdigit)) { | |
| status.SetError(cmStrCat("given a non-integer ", key, '.')); | |
| return false; | |
| } | |
| version = std::atoi(versionString.c_str()); | |
| if (version != 1) { | |
| status.SetError( | |
| cmStrCat("given an unsupported ", key, " \"", versionString, | |
| "\" (the only currently supported version is 1).")); | |
| return false; | |
| } | |
| return true; | |
| } | |
| bool validateDataVersion(std::string const& versionString, Version& version, | |
| cmExecutionStatus& status) | |
| { | |
| char const* vStart = versionString.c_str(); | |
| if (!std::all_of(versionString.begin(), versionString.end(), [](char c) { | |
| return cmsysString_isdigit(c) || c == '.'; | |
| })) { | |
| status.SetError( | |
| cmStrCat("given a malformed DATA_VERSION \"", versionString, | |
| "\". A numeric major or major.minor version is required.")); | |
| return false; | |
| } | |
| version.Major = std::atoi(vStart); | |
| version.Minor = 0; | |
| std::string::size_type pos = versionString.find('.'); | |
| if (pos != std::string::npos) { | |
| vStart += pos + 1; | |
| version.Minor = std::atoi(vStart); | |
| } | |
| if (version.Major < 1 || version.Minor < 0) { | |
| status.SetError( | |
| cmStrCat("given a malformed DATA_VERSION \"", versionString, | |
| "\". A numeric major or major.minor version is required.")); | |
| return false; | |
| } | |
| if (!cmInstrumentationQuery::ValidDataVersion(version)) { | |
| status.SetError( | |
| cmStrCat("given an unsupported DATA_VERSION \"", versionString, | |
| "\" (the maximum currently supported version is 1.2).")); | |
| return false; | |
| } | |
| return true; | |
| } | |
| template <typename E> | |
| std::function<bool(std::string const&, E&)> EnumParser( | |
| std::vector<std::string> const toString) | |
| { | |
| return [toString](std::string const& value, E& out) -> bool { | |
| for (size_t i = 0; i < toString.size(); ++i) { | |
| if (value == toString[i]) { | |
| out = (E)i; | |
| return true; | |
| } | |
| } | |
| return false; | |
| }; | |
| } | |
| } | |
| bool cmInstrumentationCommand(std::vector<std::string> const& args, | |
| cmExecutionStatus& status) | |
| { | |
| if (args.empty()) { | |
| status.SetError("must be called with arguments."); | |
| return false; | |
| } | |
| if (status.GetMakefile().GetCMakeInstance()->GetInInitialCache()) { | |
| status.GetMakefile().IssueMessage( | |
| MessageType::FATAL_ERROR, | |
| "Cannot invoke cmake_instrumentation() within an initial cache file."); | |
| return false; | |
| } | |
| struct Arguments : public ArgumentParser::ParseResult | |
| { | |
| ArgumentParser::NonEmpty<std::string> ApiVersion; | |
| ArgumentParser::NonEmpty<std::string> DataVersion; | |
| ArgumentParser::NonEmpty<std::vector<std::string>> Options; | |
| ArgumentParser::NonEmpty<std::vector<std::string>> Hooks; | |
| ArgumentParser::NonEmpty<std::vector<std::vector<std::string>>> Callbacks; | |
| ArgumentParser::NonEmpty<std::vector<std::vector<std::string>>> | |
| CustomContent; | |
| }; | |
| static auto const parser = | |
| cmArgumentParser<Arguments>{} | |
| .Bind("API_VERSION"_s, &Arguments::ApiVersion) | |
| .Bind("DATA_VERSION"_s, &Arguments::DataVersion) | |
| .Bind("OPTIONS"_s, &Arguments::Options) | |
| .Bind("HOOKS"_s, &Arguments::Hooks) | |
| .Bind("CALLBACK"_s, &Arguments::Callbacks) | |
| .Bind("CUSTOM_CONTENT"_s, &Arguments::CustomContent); | |
| std::vector<std::string> unparsedArguments; | |
| Arguments const arguments = parser.Parse(args, &unparsedArguments); | |
| if (arguments.MaybeReportError(status.GetMakefile())) { | |
| return true; | |
| } | |
| if (!unparsedArguments.empty()) { | |
| status.SetError( | |
| cmStrCat("given unknown argument \"", unparsedArguments.front(), "\".")); | |
| return false; | |
| } | |
| int apiVersion; | |
| Version dataVersion; | |
| if (!validateVersion("API_VERSION", arguments.ApiVersion, apiVersion, | |
| status) || | |
| !validateDataVersion(arguments.DataVersion, dataVersion, status)) { | |
| return false; | |
| } | |
| std::set<cmInstrumentationQuery::Option> options; | |
| auto optionParser = EnumParser<cmInstrumentationQuery::Option>( | |
| cmInstrumentationQuery::OptionString); | |
| for (auto const& arg : arguments.Options) { | |
| cmInstrumentationQuery::Option option; | |
| if (!optionParser(arg, option)) { | |
| status.SetError( | |
| cmStrCat("given invalid argument to OPTIONS \"", arg, '"')); | |
| return false; | |
| } | |
| options.insert(option); | |
| } | |
| std::set<cmInstrumentationQuery::Hook> hooks; | |
| auto hookParser = EnumParser<cmInstrumentationQuery::Hook>( | |
| cmInstrumentationQuery::HookString); | |
| for (auto const& arg : arguments.Hooks) { | |
| cmInstrumentationQuery::Hook hook; | |
| if (!hookParser(arg, hook)) { | |
| status.SetError( | |
| cmStrCat("given invalid argument to HOOKS \"", arg, '"')); | |
| return false; | |
| } | |
| hooks.insert(hook); | |
| } | |
| // Generate custom content | |
| cmInstrumentation* instrumentation = | |
| status.GetMakefile().GetCMakeInstance()->GetInstrumentation(); | |
| for (auto const& content : arguments.CustomContent) { | |
| if (content.size() != 3) { | |
| status.SetError("CUSTOM_CONTENT expected 3 arguments"); | |
| return false; | |
| } | |
| std::string const label = content[0]; | |
| std::string const type = content[1]; | |
| std::string const contentString = content[2]; | |
| Json::Value value; | |
| if (type == "STRING") { | |
| value = contentString; | |
| } else if (type == "BOOL") { | |
| value = !cmValue(contentString).IsOff(); | |
| } else if (type == "LIST") { | |
| value = Json::arrayValue; | |
| for (auto const& item : cmList(contentString)) { | |
| value.append(item); | |
| } | |
| } else if (type == "JSON") { | |
| std::istringstream iss(contentString); | |
| cmJSONState parseState(iss, &value, cmJSONState::StrictMode::Relaxed); | |
| if (!parseState.errors.empty()) { | |
| status.SetError(cmStrCat("failed to parse custom content as JSON:\n ", | |
| parseState.GetErrorMessage())); | |
| return false; | |
| } | |
| } else { | |
| status.SetError( | |
| cmStrCat("got an invalid type for CUSTOM_CONTENT: ", type)); | |
| return false; | |
| } | |
| instrumentation->AddCustomContent(content.front(), value); | |
| } | |
| // Write query file | |
| instrumentation->WriteJSONQuery(dataVersion, options, hooks, | |
| arguments.Callbacks); | |
| return true; | |
| } |