instrumentation: Revise Data Version format

Give data version the new format <major>.<minor>, so that
the version can be incremented with the introduction of new
features without bumping the major version.

Add support for loading instrumentation JSON queries of unknown
data versions without error.

Issue: #27833
This commit is contained in:
Martin Duffy
2026-05-29 11:39:26 -04:00
committed by Brad King
parent cd807309bf
commit dcf146c34d
32 changed files with 348 additions and 79 deletions

View File

@@ -19,10 +19,14 @@ This allows for configuring instrumentation at the project-level.
[CUSTOM_CONTENT <name> <type> <content>] [CUSTOM_CONTENT <name> <type> <content>]
) )
The ``API_VERSION`` and ``DATA_VERSION`` must always be given. Currently, the The ``API_VERSION`` and ``DATA_VERSION`` must always be given.
only supported value for both fields is 1. See
:ref:`cmake-instrumentation API v1` for details of the ``API_VERSION`` and ``API_VERSION`` is an integer. Currently, the only supported value is ``1``.
:ref:`cmake-instrumentation Data Version` for details of the ``DATA_VERSION``. See :ref:`cmake-instrumentation API v1` for details.
``DATA_VERSION`` is a version value of the form ``major`` or ``major.minor``.
Currently, the only supported version is ``1.0``. See
:ref:`cmake-instrumentation Data Version` for details.
Each of the optional keywords ``HOOKS``, ``OPTIONS``, and ``CALLBACK`` Each of the optional keywords ``HOOKS``, ``OPTIONS``, and ``CALLBACK``
correspond to one of the parameters to the :ref:`cmake-instrumentation v1 Query Files`. correspond to one of the parameters to the :ref:`cmake-instrumentation v1 Query Files`.
@@ -70,7 +74,7 @@ equivalent JSON query file.
cmake_instrumentation( cmake_instrumentation(
API_VERSION 1 API_VERSION 1
DATA_VERSION 1 DATA_VERSION 1.0
HOOKS postGenerate preCMakeBuild postCMakeBuild HOOKS postGenerate preCMakeBuild postCMakeBuild
OPTIONS staticSystemInformation dynamicSystemInformation trace OPTIONS staticSystemInformation dynamicSystemInformation trace
CALLBACK ${CMAKE_COMMAND} -P /path/to/handle_data.cmake CALLBACK ${CMAKE_COMMAND} -P /path/to/handle_data.cmake
@@ -91,7 +95,7 @@ equivalent JSON query file.
"staticSystemInformation", "dynamicSystemInformation", "trace" "staticSystemInformation", "dynamicSystemInformation", "trace"
], ],
"callbacks": [ "callbacks": [
"/path/to/cmake -P /path/to/handle_data.cmake" "/path/to/cmake -P /path/to/handle_data.cmake",
"/path/to/cmake -P /path/to/handle_data_2.cmake" "/path/to/cmake -P /path/to/handle_data_2.cmake"
] ]
} }

View File

@@ -229,9 +229,11 @@ request a specific Data Version, and `v1 Data Files`_ of the corresponding
version will be generated and sent to the user `Callbacks`_ defined in that version will be generated and sent to the user `Callbacks`_ defined in that
query. query.
Currently, the only supported version is ``1``. A new version number will be Currently, the only supported version is ``1.0``. A new major version number
created whenever previously included data is removed or reformatted such that will be created whenever previously included data is removed or reformatted such
scripts written to parse this data may become incompatible with the new format. that scripts written to parse this data may become incompatible with the new
format. A new minor version number will be created whenever new data becomes
available.
.. _`cmake-instrumentation v1 Query Files`: .. _`cmake-instrumentation v1 Query Files`:
@@ -245,8 +247,17 @@ These files must contain a JSON object with the following keys. The ``version``
key is required, but all other fields are optional. key is required, but all other fields are optional.
``version`` ``version``
The `Data Version`_ of snippet file to generate, an integer. Currently the only The `Data Version`_ of snippet files to generate.
supported version is ``1``.
In query files, this may be specified either as an integer major version or
as an object with ``major`` and ``minor`` members. For example, ``1`` and
``{ "major": 1, "minor": 0 }`` both request version ``1.0``. Specifying a
minor version is optional. CMake will always generate instrumentation data
for the most recent minor version, even if an earlier minor version is
requested.
Currently, the only supported version is ``1.0``. Query files with an unknown
data version will be ignored.
``callbacks`` ``callbacks``
A list of command-line strings for `Callbacks`_ to handle collected A list of command-line strings for `Callbacks`_ to handle collected
@@ -402,8 +413,8 @@ Snippet files have a filename with the syntax
``<role>-<hash>-<timestamp>.json`` and contain the following data: ``<role>-<hash>-<timestamp>.json`` and contain the following data:
``version`` ``version``
The `Data Version`_ of the snippet file, an integer. Currently the version is The `Data Version`_ of the snippet file. Currently the version is
always ``1``. always ``{ "major": 1, "minor": 0 }``.
``command`` ``command``
The full command executed. Excluded when ``role`` is ``build``. The full command executed. Excluded when ``role`` is ``build``.
@@ -507,7 +518,10 @@ Example:
.. code-block:: json .. code-block:: json
{ {
"version": 1, "version": {
"major": 1,
"minor": 0
},
"command" : "\"/usr/bin/c++\" \"-MD\" \"-MT\" \"CMakeFiles/main.dir/main.cxx.o\" \"-MF\" \"CMakeFiles/main.dir/main.cxx.o.d\" \"-o\" \"CMakeFiles/main.dir/main.cxx.o\" \"-c\" \"<src>/main.cxx\"", "command" : "\"/usr/bin/c++\" \"-MD\" \"-MT\" \"CMakeFiles/main.dir/main.cxx.o\" \"-MF\" \"CMakeFiles/main.dir/main.cxx.o.d\" \"-o\" \"CMakeFiles/main.dir/main.cxx.o\" \"-c\" \"<src>/main.cxx\"",
"role" : "compile", "role" : "compile",
"result" : 1, "result" : 1,
@@ -538,8 +552,8 @@ generated whenever `Indexing`_ occurs and deleted after any user-specified
`Callbacks`_ are executed. `Callbacks`_ are executed.
``version`` ``version``
The `Data Version`_ of the index file, an integer. Currently the version is The `Data Version`_ of the index file. Currently this is always written as:
always ``1``. ``{ "major": 1, "minor": 0 }``.
``buildDir`` ``buildDir``
The build directory of the CMake project. The build directory of the CMake project.
@@ -604,7 +618,10 @@ Example:
.. code-block:: json .. code-block:: json
{ {
"version": 1, "version": {
"major": 1,
"minor": 0
},
"hook": "manual", "hook": "manual",
"buildDir": "<build>", "buildDir": "<build>",
"dataDir": "<build>/.cmake/instrumentation/v1/data", "dataDir": "<build>/.cmake/instrumentation/v1/data",
@@ -637,6 +654,10 @@ corresponding to the CMake invocation responsible for generating its command.
Each CMake content file contains the following: Each CMake content file contains the following:
``version``
The `Data Version`_ of the content file. Currently the version is
always ``{ "major": 1, "minor": 0 }``.
``project`` ``project``
The value of :variable:`CMAKE_PROJECT_NAME`. The value of :variable:`CMAKE_PROJECT_NAME`.

View File

@@ -34,11 +34,23 @@
}, },
"properties": { "properties": {
"version": { "version": {
"type": "integer",
"description": "The data version of the index file.", "description": "The data version of the index file.",
"enum": [ "type": "object",
1 "required": [
] "major",
"minor"
],
"properties": {
"major": {
"type": "integer",
"const": 1
},
"minor": {
"type": "integer",
"const": 0
}
},
"additionalProperties": false
}, },
"buildDir": { "buildDir": {
"type": "string", "type": "string",

View File

@@ -4,10 +4,29 @@
"required": ["version"], "required": ["version"],
"properties": { "properties": {
"version": { "version": {
"type": "integer",
"description": "The data version of snippet file to generate.", "description": "The data version of snippet file to generate.",
"enum": [ "oneOf": [
1 {
"type": "integer",
"const": 1
},
{
"type": "object",
"required": [
"major"
],
"properties": {
"major": {
"type": "integer",
"const": 1
},
"minor": {
"type": "integer",
"const": 0
}
},
"additionalProperties": false
}
] ]
}, },
"callbacks": { "callbacks": {

View File

@@ -44,6 +44,11 @@
using LoadQueriesAfter = cmInstrumentation::LoadQueriesAfter; using LoadQueriesAfter = cmInstrumentation::LoadQueriesAfter;
namespace {
cmInstrumentationQuery::Version latestDataVersion =
cmInstrumentationQuery::LatestDataVersion();
}
std::map<std::string, std::string> cmInstrumentation::cdashSnippetsMap = { std::map<std::string, std::string> cmInstrumentation::cdashSnippetsMap = {
{ {
"configure", "configure",
@@ -145,7 +150,7 @@ void cmInstrumentation::CheckCDashVariable()
options_.insert(cmInstrumentationQuery::Option::CDashVerbose); options_.insert(cmInstrumentationQuery::Option::CDashVerbose);
} }
std::set<cmInstrumentationQuery::Hook> hooks_; std::set<cmInstrumentationQuery::Hook> hooks_;
this->WriteJSONQuery(options_, hooks_, {}); this->WriteJSONQuery(latestDataVersion, options_, hooks_, {});
} }
} }
@@ -198,12 +203,12 @@ bool cmInstrumentation::HasErrors() const
} }
void cmInstrumentation::WriteJSONQuery( void cmInstrumentation::WriteJSONQuery(
cmInstrumentationQuery::Version dataVersion,
std::set<cmInstrumentationQuery::Option> const& options_, std::set<cmInstrumentationQuery::Option> const& options_,
std::set<cmInstrumentationQuery::Hook> const& hooks_, std::set<cmInstrumentationQuery::Hook> const& hooks_,
std::vector<std::vector<std::string>> const& callbacks_) std::vector<std::vector<std::string>> const& callbacks_)
{ {
Json::Value root; Json::Value root;
root["version"] = 1;
root["options"] = Json::arrayValue; root["options"] = Json::arrayValue;
for (auto const& option : options_) { for (auto const& option : options_) {
root["options"].append(cmInstrumentationQuery::OptionString[option]); root["options"].append(cmInstrumentationQuery::OptionString[option]);
@@ -217,7 +222,7 @@ void cmInstrumentation::WriteJSONQuery(
root["callbacks"].append(cmInstrumentation::GetCommandStr(callback)); root["callbacks"].append(cmInstrumentation::GetCommandStr(callback));
} }
this->WriteInstrumentationJson( this->WriteInstrumentationJson(
root, "query/generated", dataVersion, root, "query/generated",
cmStrCat("query-", this->writtenJsonQueries++, ".json")); cmStrCat("query-", this->writtenJsonQueries++, ".json"));
} }
@@ -236,7 +241,7 @@ void cmInstrumentation::WriteCMakeContent(
root["project"] = root["project"] =
gg->GetCMakeInstance()->GetCacheDefinition("CMAKE_PROJECT_NAME").GetCStr(); gg->GetCMakeInstance()->GetCacheDefinition("CMAKE_PROJECT_NAME").GetCStr();
this->WriteInstrumentationJson( this->WriteInstrumentationJson(
root, "data/content", latestDataVersion, root, "data/content",
cmStrCat("cmake-", this->ComputeSuffixTime(), ".json")); cmStrCat("cmake-", this->ComputeSuffixTime(), ".json"));
} }
@@ -362,6 +367,8 @@ int cmInstrumentation::CollectTimingData(cmInstrumentationQuery::Hook hook)
cmsys::Directory d; cmsys::Directory d;
std::string last_index_name = std::string last_index_name =
this->GetFileByTimestamp(LatestOrOldest::Latest, "index", index_name); this->GetFileByTimestamp(LatestOrOldest::Latest, "index", index_name);
std::string last_index_path =
cmStrCat(this->dataDir, "/index/", last_index_name);
if (d.Load(this->dataDir)) { if (d.Load(this->dataDir)) {
for (unsigned int i = 0; i < d.GetNumberOfFiles(); i++) { for (unsigned int i = 0; i < d.GetNumberOfFiles(); i++) {
std::string fpath = d.GetFilePath(i); std::string fpath = d.GetFilePath(i);
@@ -379,18 +386,16 @@ int cmInstrumentation::CollectTimingData(cmInstrumentationQuery::Hook hook)
index["hook"] = cmInstrumentationQuery::HookString[hook]; index["hook"] = cmInstrumentationQuery::HookString[hook];
index["dataDir"] = this->dataDir; index["dataDir"] = this->dataDir;
index["buildDir"] = this->binaryDir; index["buildDir"] = this->binaryDir;
index["version"] = 1;
if (this->HasOption( if (this->HasOption(
cmInstrumentationQuery::Option::StaticSystemInformation)) { cmInstrumentationQuery::Option::StaticSystemInformation)) {
this->InsertStaticSystemInformation(index); this->InsertStaticSystemInformation(index);
} }
for (auto const& file : files) { for (auto const& file : files) {
if (last_index_name.empty()) { if (last_index_name.empty()) {
index["snippets"].append(file.first); index["snippets"].append(file.first);
} else { } else {
int compare; int compare;
std::string last_index_path =
cmStrCat(this->dataDir, "/index/", last_index_name);
cmSystemTools::FileTimeCompare(file.second, last_index_path, &compare); cmSystemTools::FileTimeCompare(file.second, last_index_path, &compare);
if (compare == 1) { if (compare == 1) {
index["snippets"].append(file.first); index["snippets"].append(file.first);
@@ -406,13 +411,14 @@ int cmInstrumentation::CollectTimingData(cmInstrumentationQuery::Hook hook)
} }
// Write index file // Write index file
this->WriteInstrumentationJson(index, "data/index", index_name); this->WriteInstrumentationJson(latestDataVersion, index, "data/index",
index_name);
// Execute callbacks // Execute callbacks
for (auto& cb : this->callbacks) { for (auto const& cb : this->callbacks) {
cmSystemTools::RunSingleCommand(cmStrCat(cb, " \"", index_path, '"'), cmSystemTools::RunSingleCommand(
nullptr, nullptr, nullptr, nullptr, cmStrCat(cb.Command, " \"", index_path, '"'), nullptr, nullptr, nullptr,
cmSystemTools::OUTPUT_PASSTHROUGH); nullptr, cmSystemTools::OUTPUT_PASSTHROUGH);
} }
// Special case for CDash collation // Special case for CDash collation
@@ -545,10 +551,14 @@ Json::Value cmInstrumentation::ReadJsonSnippet(std::string const& file_name)
return snippetData; return snippetData;
} }
void cmInstrumentation::WriteInstrumentationJson(Json::Value& root, void cmInstrumentation::WriteInstrumentationJson(
std::string const& subdir, cmInstrumentationQuery::Version version, Json::Value& root,
std::string const& file_name) std::string const& subdir, std::string const& file_name)
{ {
root["version"] = Json::objectValue;
root["version"]["major"] = version.Major;
root["version"]["minor"] = version.Minor;
Json::StreamWriterBuilder wbuilder; Json::StreamWriterBuilder wbuilder;
wbuilder["indentation"] = "\t"; wbuilder["indentation"] = "\t";
std::unique_ptr<Json::StreamWriter> JsonWriter = std::unique_ptr<Json::StreamWriter> JsonWriter =
@@ -581,7 +591,6 @@ std::string cmInstrumentation::InstrumentTest(
// Store command info // Store command info
Json::Value root(this->preTestStats); Json::Value root(this->preTestStats);
std::string command_str = cmStrCat(command, ' ', GetCommandStr(args)); std::string command_str = cmStrCat(command, ' ', GetCommandStr(args));
root["version"] = 1;
root["command"] = command_str; root["command"] = command_str;
root["role"] = "test"; root["role"] = "test";
root["testName"] = name; root["testName"] = name;
@@ -603,7 +612,7 @@ std::string cmInstrumentation::InstrumentTest(
"test-", "test-",
this->ComputeSuffixHash(cmStrCat(command_str, info.GetProcessId())), '-', this->ComputeSuffixHash(cmStrCat(command_str, info.GetProcessId())), '-',
this->ComputeSuffixTime(endTime), ".json"); this->ComputeSuffixTime(endTime), ".json");
this->WriteInstrumentationJson(root, "data", file_name); this->WriteInstrumentationJson(latestDataVersion, root, "data", file_name);
return file_name; return file_name;
} }
@@ -637,7 +646,6 @@ int cmInstrumentation::InstrumentCommand(
if (!command_str.empty()) { if (!command_str.empty()) {
root["command"] = command_str; root["command"] = command_str;
} }
root["version"] = 1;
// Pre-Command // Pre-Command
auto steady_start = std::chrono::steady_clock::now(); auto steady_start = std::chrono::steady_clock::now();
@@ -759,11 +767,12 @@ int cmInstrumentation::InstrumentCommand(
} else { } else {
addCMakeContent(it->second); addCMakeContent(it->second);
} }
this->WriteInstrumentationJson(it->second, "data", it->first); this->WriteInstrumentationJson(latestDataVersion, it->second, "data",
it->first);
} }
this->configureSnippetData.clear(); this->configureSnippetData.clear();
} }
this->WriteInstrumentationJson(root, "data", file_name); this->WriteInstrumentationJson(latestDataVersion, root, "data", file_name);
} }
return ret; return ret;
} }

View File

@@ -31,6 +31,8 @@ class cmGlobalGenerator;
class cmInstrumentation class cmInstrumentation
{ {
public: public:
using Callback = cmInstrumentationQuery::Callback;
enum class LoadQueriesAfter enum class LoadQueriesAfter
{ {
Yes, Yes,
@@ -60,7 +62,8 @@ public:
bool HasHook(cmInstrumentationQuery::Hook hook) const; bool HasHook(cmInstrumentationQuery::Hook hook) const;
bool ReadJSONQueries(std::string const& directory); bool ReadJSONQueries(std::string const& directory);
void ReadJSONQuery(std::string const& file); void ReadJSONQuery(std::string const& file);
void WriteJSONQuery(std::set<cmInstrumentationQuery::Option> const& options, void WriteJSONQuery(cmInstrumentationQuery::Version dataVersion,
std::set<cmInstrumentationQuery::Option> const& options,
std::set<cmInstrumentationQuery::Hook> const& hooks, std::set<cmInstrumentationQuery::Hook> const& hooks,
std::vector<std::vector<std::string>> const& callback); std::vector<std::vector<std::string>> const& callback);
void AddCustomContent(std::string const& name, Json::Value const& contents); void AddCustomContent(std::string const& name, Json::Value const& contents);
@@ -90,7 +93,8 @@ private:
Json::Value ReadJsonSnippet(std::string const& file_name); Json::Value ReadJsonSnippet(std::string const& file_name);
bool AcquireLock(std::string const& lock_file, cmFileLock& lock, bool AcquireLock(std::string const& lock_file, cmFileLock& lock,
unsigned long timeout); unsigned long timeout);
void WriteInstrumentationJson(Json::Value& index, void WriteInstrumentationJson(cmInstrumentationQuery::Version version,
Json::Value& index,
std::string const& directory, std::string const& directory,
std::string const& file_name); std::string const& file_name);
void InsertStaticSystemInformation(Json::Value& index); void InsertStaticSystemInformation(Json::Value& index);
@@ -122,7 +126,7 @@ private:
std::string dataDir; std::string dataDir;
std::set<cmInstrumentationQuery::Option> options; std::set<cmInstrumentationQuery::Option> options;
std::set<cmInstrumentationQuery::Hook> hooks; std::set<cmInstrumentationQuery::Hook> hooks;
std::vector<std::string> callbacks; std::vector<Callback> callbacks;
std::vector<std::string> queryFiles; std::vector<std::string> queryFiles;
static std::map<std::string, std::string> cdashSnippetsMap; static std::map<std::string, std::string> cdashSnippetsMap;
Json::Value preTestStats; Json::Value preTestStats;

View File

@@ -29,6 +29,8 @@ file LICENSE.rst or https://cmake.org/licensing for details. */
namespace { namespace {
using Version = cmInstrumentationQuery::Version;
bool validateVersion(std::string const& key, std::string const& versionString, bool validateVersion(std::string const& key, std::string const& versionString,
int& version, cmExecutionStatus& status) int& version, cmExecutionStatus& status)
{ {
@@ -47,6 +49,45 @@ bool validateVersion(std::string const& key, std::string const& versionString,
return true; 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 only currently supported version is 1.0)."));
return false;
}
return true;
}
template <typename E> template <typename E>
std::function<bool(std::string const&, E&)> EnumParser( std::function<bool(std::string const&, E&)> EnumParser(
std::vector<std::string> const toString) std::vector<std::string> const toString)
@@ -110,11 +151,10 @@ bool cmInstrumentationCommand(std::vector<std::string> const& args,
return false; return false;
} }
int apiVersion; int apiVersion;
int dataVersion; Version dataVersion;
if (!validateVersion("API_VERSION", arguments.ApiVersion, apiVersion, if (!validateVersion("API_VERSION", arguments.ApiVersion, apiVersion,
status) || status) ||
!validateVersion("DATA_VERSION", arguments.DataVersion, dataVersion, !validateDataVersion(arguments.DataVersion, dataVersion, status)) {
status)) {
return false; return false;
} }
@@ -182,7 +222,8 @@ bool cmInstrumentationCommand(std::vector<std::string> const& args,
} }
// Write query file // Write query file
instrumentation->WriteJSONQuery(options, hooks, arguments.Callbacks); instrumentation->WriteJSONQuery(dataVersion, options, hooks,
arguments.Callbacks);
return true; return true;
} }

View File

@@ -3,7 +3,6 @@
#include <algorithm> #include <algorithm>
#include <ctime> #include <ctime>
#include <functional> #include <functional>
#include <iostream>
#include <iterator> #include <iterator>
#include <set> #include <set>
#include <vector> #include <vector>
@@ -47,6 +46,7 @@ JsonErrors::ErrorGenerator InvalidRootQueryObject(
}; };
using JSONHelperBuilder = cmJSONHelperBuilder; using JSONHelperBuilder = cmJSONHelperBuilder;
using Version = cmInstrumentationQuery::Version;
template <typename E> template <typename E>
static std::function<bool(E&, Json::Value const*, cmJSONState*)> EnumHelper( static std::function<bool(E&, Json::Value const*, cmJSONState*)> EnumHelper(
@@ -67,7 +67,7 @@ static std::function<bool(E&, Json::Value const*, cmJSONState*)> EnumHelper(
} }
static auto const OptionHelper = EnumHelper<cmInstrumentationQuery::Option>( static auto const OptionHelper = EnumHelper<cmInstrumentationQuery::Option>(
cmInstrumentationQuery::OptionString, "option"); cmInstrumentationQuery::OptionString, "option");
static auto const QueryListHelper = static auto const OptionListHelper =
JSONHelperBuilder::Vector<cmInstrumentationQuery::Option>( JSONHelperBuilder::Vector<cmInstrumentationQuery::Option>(
ErrorMessages::InvalidArray, OptionHelper); ErrorMessages::InvalidArray, OptionHelper);
static auto const HookHelper = EnumHelper<cmInstrumentationQuery::Hook>( static auto const HookHelper = EnumHelper<cmInstrumentationQuery::Hook>(
@@ -78,7 +78,36 @@ static auto const HookListHelper =
static auto const CallbackHelper = JSONHelperBuilder::String(); static auto const CallbackHelper = JSONHelperBuilder::String();
static auto const CallbackListHelper = JSONHelperBuilder::Vector<std::string>( static auto const CallbackListHelper = JSONHelperBuilder::Vector<std::string>(
ErrorMessages::InvalidArray, CallbackHelper); ErrorMessages::InvalidArray, CallbackHelper);
static auto const VersionHelper = JSONHelperBuilder::Int();
JsonErrors::ErrorGenerator InvalidVersionObject(
JsonErrors::ObjectError errorType, Json::Value::Members const& extraFields)
{
return JsonErrors::INVALID_NAMED_OBJECT(
[](Json::Value const*, cmJSONState*) -> std::string {
return "version object";
})(errorType, extraFields);
}
static auto const VersionObjectHelper =
JSONHelperBuilder::Object<Version>(InvalidVersionObject, false)
.Bind("major"_s, &Version::Major, JSONHelperBuilder::Int(), true)
.Bind("minor"_s, &Version::Minor, JSONHelperBuilder::Int(), false);
bool VersionHelper(Version& out, Json::Value const* value, cmJSONState* state)
{
out.Minor = 0;
if (value->isInt()) {
out.Major = value->asInt();
} else if (value->isObject()) {
if (!VersionObjectHelper(out, value, state)) {
return false;
}
} else {
state->AddErrorAtValue("Version must be an integer or object", value);
return false;
}
return true;
}
using QueryRoot = cmInstrumentationQuery::QueryJSONRoot; using QueryRoot = cmInstrumentationQuery::QueryJSONRoot;
@@ -86,22 +115,35 @@ static auto const QueryRootHelper =
JSONHelperBuilder::Object<QueryRoot>(ErrorMessages::InvalidRootQueryObject, JSONHelperBuilder::Object<QueryRoot>(ErrorMessages::InvalidRootQueryObject,
false) false)
.Bind("version"_s, &QueryRoot::version, VersionHelper, true) .Bind("version"_s, &QueryRoot::version, VersionHelper, true)
.Bind("options"_s, &QueryRoot::options, QueryListHelper, false) .Bind("options"_s, &QueryRoot::options, OptionListHelper, false)
.Bind("hooks"_s, &QueryRoot::hooks, HookListHelper, false) .Bind("hooks"_s, &QueryRoot::hooks, HookListHelper, false)
.Bind("callbacks"_s, &QueryRoot::callbacks, CallbackListHelper, false); .Bind("callbacks"_s, &QueryRoot::callbacks, CallbackListHelper, false);
static auto const QueryRootVersionOnlyHelper =
JSONHelperBuilder::Object<QueryRoot>(ErrorMessages::InvalidRootQueryObject,
true)
.Bind("version"_s, &QueryRoot::version, VersionHelper, true);
bool cmInstrumentationQuery::ReadJSON(std::string const& filename, bool cmInstrumentationQuery::ReadJSON(std::string const& filename,
std::string& errorMessage, std::string& errorMessage,
std::set<Option>& options, std::set<Option>& options,
std::set<Hook>& hooks, std::set<Hook>& hooks,
std::vector<std::string>& callbacks) std::vector<Callback>& callbacks)
{ {
Json::Value root; Json::Value root;
this->parseState = cmJSONState(filename, &root); this->parseState = cmJSONState(filename, &root);
if (!this->parseState.errors.empty()) { if (!this->parseState.errors.empty()) {
std::cerr << this->parseState.GetErrorMessage(true) << std::endl; errorMessage = this->parseState.GetErrorMessage(true);
return false; return false;
} }
if (!QueryRootVersionOnlyHelper(this->queryRoot, &root, &this->parseState)) {
errorMessage = this->parseState.GetErrorMessage(true);
return false;
}
if (!ValidDataVersion(this->queryRoot.version)) {
// Ignore invalid data versions
return true;
}
if (!QueryRootHelper(this->queryRoot, &root, &this->parseState)) { if (!QueryRootHelper(this->queryRoot, &root, &this->parseState)) {
errorMessage = this->parseState.GetErrorMessage(true); errorMessage = this->parseState.GetErrorMessage(true);
return false; return false;
@@ -110,7 +152,22 @@ bool cmInstrumentationQuery::ReadJSON(std::string const& filename,
std::inserter(options, options.end())); std::inserter(options, options.end()));
std::move(this->queryRoot.hooks.begin(), this->queryRoot.hooks.end(), std::move(this->queryRoot.hooks.begin(), this->queryRoot.hooks.end(),
std::inserter(hooks, hooks.end())); std::inserter(hooks, hooks.end()));
std::move(this->queryRoot.callbacks.begin(), this->queryRoot.callbacks.end(), for (auto const& callback : this->queryRoot.callbacks) {
std::back_inserter(callbacks)); callbacks.push_back({ callback, this->queryRoot.version });
}
return true; return true;
} }
bool cmInstrumentationQuery::ValidDataVersion(Version version)
{
auto const latest = LatestDataVersion();
return version.Major == latest.Major && version.Minor == latest.Minor;
}
Version cmInstrumentationQuery::LatestDataVersion()
{
Version latest;
latest.Major = 1;
latest.Minor = 0;
return latest;
}

View File

@@ -37,18 +37,32 @@ public:
}; };
static std::vector<std::string> const HookString; static std::vector<std::string> const HookString;
struct Version
{
int Major = 0;
int Minor = 0;
};
struct Callback
{
std::string Command;
Version DataVersion;
};
struct QueryJSONRoot struct QueryJSONRoot
{ {
std::vector<cmInstrumentationQuery::Option> options; std::vector<cmInstrumentationQuery::Option> options;
std::vector<cmInstrumentationQuery::Hook> hooks; std::vector<cmInstrumentationQuery::Hook> hooks;
std::vector<std::string> callbacks; std::vector<std::string> callbacks;
int version; Version version;
}; };
cmInstrumentationQuery() = default; cmInstrumentationQuery() = default;
bool ReadJSON(std::string const& file, std::string& errorMessage, bool ReadJSON(std::string const& file, std::string& errorMessage,
std::set<Option>& options, std::set<Hook>& hooks, std::set<Option>& options, std::set<Hook>& hooks,
std::vector<std::string>& callbacks); std::vector<Callback>& callbacks);
QueryJSONRoot queryRoot; QueryJSONRoot queryRoot;
cmJSONState parseState; cmJSONState parseState;
static Version LatestDataVersion();
static bool ValidDataVersion(Version version);
}; };

View File

@@ -208,6 +208,18 @@ instrument(empty BAD_QUERY
instrument(bad-version BAD_QUERY instrument(bad-version BAD_QUERY
CHECK_SCRIPT check-query-dir.cmake CHECK_SCRIPT check-query-dir.cmake
) )
instrument(bad-version-major BAD_QUERY
CHECK_SCRIPT check-query-dir.cmake
)
instrument(bad-version-minor BAD_QUERY
CHECK_SCRIPT check-query-dir.cmake
)
instrument(bad-version-object BAD_QUERY
CHECK_SCRIPT check-query-dir.cmake
)
instrument(hooks-invalid-version-ignored BUILD
CHECK_SCRIPT check-hooks-invalid-version-ignored.cmake
)
# Verify Hooks Run and Index File # Verify Hooks Run and Index File
instrument(hooks-1 BUILD INSTALL TEST STATIC_QUERY instrument(hooks-1 BUILD INSTALL TEST STATIC_QUERY
@@ -249,6 +261,7 @@ instrument(cmake-command-data
) )
instrument(cmake-command-bad-api-version) instrument(cmake-command-bad-api-version)
instrument(cmake-command-bad-data-version) instrument(cmake-command-bad-data-version)
instrument(cmake-command-unsupported-data-version)
instrument(cmake-command-missing-version) instrument(cmake-command-missing-version)
instrument(cmake-command-bad-arg) instrument(cmake-command-bad-arg)
instrument(cmake-command-parallel-install instrument(cmake-command-parallel-install

View File

@@ -0,0 +1 @@
1

View File

@@ -0,0 +1,5 @@
^CMake Error: Could not load instrumentation queries from [^
]+:
bad-version-object\.json:4: Invalid extra field "patch" in version object
"patch": 0
\^

View File

@@ -15,6 +15,11 @@ set(firstFile "")
foreach(content_file IN LISTS content_files) foreach(content_file IN LISTS content_files)
read_json("${content_file}" contents) read_json("${content_file}" contents)
# Check version
string(JSON version GET "${contents}" version)
json_assert_key("${content_file}" "${version}" major "1")
json_assert_key("${content_file}" "${version}" minor "0")
# Check project name # Check project name
json_assert_key("${content_file}" "${contents}" project "instrumentation") json_assert_key("${content_file}" "${contents}" project "instrumentation")

View File

@@ -0,0 +1,4 @@
if (EXISTS ${v1}/postCMakeBuild.hook)
string(APPEND RunCMake_TEST_FAILED
"Invalid-version query should have been ignored, but postCMakeBuild hook ran\n")
endif()

View File

@@ -1,5 +1,6 @@
CMake Error at [^ CMake Error at [^
]*\(cmake_instrumentation\): ]*\(cmake_instrumentation\):
cmake_instrumentation given a non-integer DATA_VERSION\. cmake_instrumentation given a malformed DATA_VERSION "NOT_AN_INT"\. A
numeric major or major\.minor version is required\.
Call Stack \(most recent call first\): Call Stack \(most recent call first\):
CMakeLists\.txt:5 \(include\) CMakeLists\.txt:5 \(include\)

View File

@@ -1,6 +1,6 @@
CMake Error at [^ CMake Error at [^
]*\(cmake_instrumentation\): ]*\(cmake_instrumentation\):
cmake_instrumentation given an unsupported DATA_VERSION "" \(the only cmake_instrumentation given a malformed DATA_VERSION ""\. A numeric major
currently supported version is 1\)\. or major\.minor version is required\.
Call Stack \(most recent call first\): Call Stack \(most recent call first\):
CMakeLists\.txt:5 \(include\) CMakeLists\.txt:5 \(include\)

View File

@@ -0,0 +1,6 @@
CMake Error at [^
]*\(cmake_instrumentation\):
cmake_instrumentation given an unsupported DATA_VERSION "1\.1" \(the only
currently supported version is 1\.0\)\.
Call Stack \(most recent call first\):
CMakeLists\.txt:5 \(include\)

View File

@@ -37,11 +37,15 @@ endfunction()
init_query_var(STATIC_QUERY hasStaticInfo) init_query_var(STATIC_QUERY hasStaticInfo)
init_query_var(TRACE_QUERY hasTrace) init_query_var(TRACE_QUERY hasTrace)
cmake_path(GET index PARENT_PATH indexDir)
cmake_path(GET indexDir PARENT_PATH dataDir)
cmake_path(GET dataDir PARENT_PATH v1)
read_json("${index}" contents) read_json("${index}" contents)
string(JSON hook GET "${contents}" hook) string(JSON hook GET "${contents}" hook)
# Output is verified by *-stdout.txt files that the HOOK is run # Output is verified by *-stdout.txt files that the HOOK is run
message(STATUS ${hook}) message(STATUS ${hook})
# Not a check-*.cmake script, this is called as an instrumentation CALLBACK # Not a check-*.cmake script, this is called as an instrumentation CALLBACK
set(ERROR_MESSAGE "") set(ERROR_MESSAGE "")
function(add_error error) function(add_error error)
@@ -61,14 +65,17 @@ if (RunCMake_TEST_FAILED)
endif() endif()
json_has_key("${index}" "${contents}" version) json_has_key("${index}" "${contents}" version)
string(JSON version_major GET "${contents}" version major)
string(JSON version_minor GET "${contents}" version minor)
if (NOT version_major EQUAL 1 OR NOT version_minor EQUAL 0)
add_error("Version must be 1.0, got: ${version_major}.${version_minor}")
endif()
json_has_key("${index}" "${contents}" buildDir) json_has_key("${index}" "${contents}" buildDir)
json_has_key("${index}" "${contents}" dataDir) json_has_key("${index}" "${contents}" dataDir)
json_has_key("${index}" "${contents}" snippets) json_has_key("${index}" "${contents}" snippets)
if (NOT version EQUAL 1)
add_error("Version must be 1, got: ${version}")
endif()
string(JSON n_snippets LENGTH "${snippets}") string(JSON n_snippets LENGTH "${snippets}")
math(EXPR snippets_range "${n_snippets}-1") math(EXPR snippets_range "${n_snippets}-1")
@@ -194,7 +201,6 @@ if (NOT hasStaticInfo STREQUAL UNEXPECTED)
endforeach() endforeach()
endif() endif()
get_filename_component(v1 ${dataDir} DIRECTORY)
if (EXISTS ${v1}/${hook}.hook) if (EXISTS ${v1}/${hook}.hook)
add_error("Received multiple triggers of the same hook: ${hook}") add_error("Received multiple triggers of the same hook: ${hook}")
endif() endif()

View File

@@ -0,0 +1,3 @@
{
"version": 2
}

View File

@@ -0,0 +1,6 @@
{
"version": {
"major": 1,
"minor": 1
}
}

View File

@@ -0,0 +1,6 @@
{
"version": {
"major": 1,
"patch": 0
}
}

View File

@@ -1,3 +1,6 @@
{ {
"version": 0 "version": {
"major": 1,
"minor": 1
}
} }

View File

@@ -1,5 +1,8 @@
{ {
"version": 1, "version": {
"major": 1,
"minor": 0
},
"options": [ "options": [
"staticSystemInformation", "staticSystemInformation",
"dynamicSystemInformation" "dynamicSystemInformation"

View File

@@ -0,0 +1,4 @@
cmake_instrumentation(
API_VERSION 1
DATA_VERSION 1.1
)

View File

@@ -14,7 +14,7 @@
# Query 2 # Query 2
cmake_instrumentation( cmake_instrumentation(
API_VERSION 1 API_VERSION 1
DATA_VERSION 1 DATA_VERSION 1.0
HOOKS postCMakeBuild HOOKS postCMakeBuild
OPTIONS staticSystemInformation dynamicSystemInformation trace OPTIONS staticSystemInformation dynamicSystemInformation trace
CALLBACK ${CMAKE_COMMAND} -E echo callback2 CALLBACK ${CMAKE_COMMAND} -E echo callback2

View File

@@ -2,5 +2,9 @@
"callbacks" : [], "callbacks" : [],
"hooks" : [], "hooks" : [],
"options" : [], "options" : [],
"version": 1 "version" :
{
"major" : 1,
"minor" : 0
}
} }

View File

@@ -11,5 +11,9 @@
"cdashSubmit", "cdashSubmit",
"cdashVerbose" "cdashVerbose"
], ],
"version" : 1 "version" :
{
"major" : 1,
"minor" : 0
}
} }

View File

@@ -14,5 +14,9 @@
"dynamicSystemInformation", "dynamicSystemInformation",
"trace" "trace"
], ],
"version": 1 "version" :
{
"major" : 1,
"minor" : 0
}
} }

View File

@@ -1,5 +1,5 @@
{ {
"version": 1, "version": { "major": 1 },
"hooks": ["postGenerate", "postCMakeBuild", "postCTest"], "hooks": ["postGenerate", "postCMakeBuild", "postCTest"],
"callbacks": ["@GET_HOOK@"] "callbacks": ["@GET_HOOK@"]
} }

View File

@@ -0,0 +1,8 @@
{
"version": {
"major": 1,
"minor": 1
},
"hooks": ["postCMakeBuild"],
"callbacks": ["@GET_HOOK@"]
}

View File

@@ -75,9 +75,10 @@ endfunction()
function(verify_snippet_data snippet contents) function(verify_snippet_data snippet contents)
snippet_has_fields("${snippet}" "${contents}") snippet_has_fields("${snippet}" "${contents}")
snippet_valid_timing("${contents}") snippet_valid_timing("${contents}")
string(JSON version GET "${contents}" version) string(JSON version_major GET "${contents}" version major)
if (NOT version EQUAL 1) string(JSON version_minor GET "${contents}" version minor)
json_error("${snippet}" "Version must be 1, got: ${version}") if (NOT version_major EQUAL 1 OR NOT version_minor EQUAL 0)
json_error("${snippet}" "Version must be 1.0, got: ${version_major}.${version_minor}")
endif() endif()
get_filename_component(filename "${snippet}" NAME) get_filename_component(filename "${snippet}" NAME)
string(JSON result GET "${contents}" result) string(JSON result GET "${contents}" result)