blob: 8d0813f22a65753cb38ba23c7c88a6a2312149e6 [file] [log] [blame] [edit]
/* 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 <cctype>
#include <cstdlib>
#include <functional>
#include <set>
#include <sstream>
#include <cmext/string_view>
#include <cm3p/json/reader.h>
#include <cm3p/json/value.h>
#include "cmArgumentParser.h"
#include "cmArgumentParserTypes.h"
#include "cmExecutionStatus.h"
#include "cmExperimental.h"
#include "cmInstrumentation.h"
#include "cmInstrumentationQuery.h"
#include "cmList.h"
#include "cmMakefile.h"
#include "cmStringAlgorithms.h"
#include "cmValue.h"
#include "cmake.h"
namespace {
bool isCharDigit(char ch)
{
return std::isdigit(static_cast<unsigned char>(ch));
}
bool validateVersion(std::string const& key, std::string const& versionString,
int& version, cmExecutionStatus& status)
{
if (!std::all_of(versionString.begin(), versionString.end(), isCharDigit)) {
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;
}
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 (status->GetMakefile().GetPropertyKeys) {
if (!cmExperimental::HasSupportEnabled(
status.GetMakefile(), cmExperimental::Feature::Instrumentation)) {
status.SetError(
"requires the experimental Instrumentation flag to be enabled");
return false;
}
if (args.empty()) {
status.SetError("must be called with arguments.");
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("given unknown argument \"" + unparsedArguments.front() +
"\".");
return false;
}
int apiVersion;
int dataVersion;
if (!validateVersion("API_VERSION", arguments.ApiVersion, apiVersion,
status) ||
!validateVersion("DATA_VERSION", 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") {
Json::CharReaderBuilder builder;
std::istringstream iss(contentString);
if (!Json::parseFromStream(builder, iss, &value, nullptr)) {
status.SetError(
cmStrCat("failed to parse custom content as JSON: ", contentString));
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(options, hooks, arguments.Callbacks);
return true;
}