trace: Add JSON output format

Add a new `--trace-format=` flag, to enable the new JSON trace
output format. This new format is easier to parse by machines
than the existing format. This new format also removes the
ambiguity of the whitespace in the "old" format (e.g. is that
whitespace part of a file path, or does it seperate arguments)
This commit is contained in:
Daniel Mensinger
2019-11-30 12:48:26 +01:00
parent 10fea25139
commit 482497e0de
6 changed files with 196 additions and 9 deletions

View File

@@ -251,6 +251,66 @@ Options
Like ``--trace``, but with variables expanded.
``--trace-format=<format>``
Put cmake in trace mode and sets the trace output format.
``<format>`` can be one of the following values.
``human``
Prints each trace line in a human-readable format. This is the
default format.
``json``
Prints each line as a separate JSON document. Each document is
separated by a newline ( ``\n`` ). It is guaranteed that no
newline characters will be present inside a JSON document.
JSON trace format:
.. code-block:: json
{
"file": "/full/path/to/the/CMake/file.txt",
"line": 0,
"cmd": "add_executable",
"args": ["foo", "bar"]
}
The members are:
``file``
The full path to the CMake source file where the function
was called.
``line``
The line in `file` of the function call.
``cmd``
The name of the function that was called.
``args``
A string list of all function parameters.
Additionally, the first JSON document outputted contains the
``version`` key for the current major and minor version of the
JSON trace format:
.. code-block:: json
{
"version": {
"major": 1,
"minor": 0
}
}
The members are:
``version``
Indicates the version of the JSON format. The version has a
major and minor components following semantic version conventions.
``--trace-source=<file>``
Put cmake in trace mode, but output only lines of a specified file.

View File

@@ -0,0 +1,7 @@
json-trace
----------
* :manual:`cmake(1)` gained a ``--trace-format`` command line option that
can be used to set the ``--trace`` output format. Currently, the old
human readable and the new JSON format are supported. The new JSON format
is easier to parse automatically, than the existing format.

View File

@@ -7,6 +7,7 @@
#include <algorithm>
#include <cassert>
#include <cctype>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
@@ -20,6 +21,8 @@
#include "cmsys/FStream.hxx"
#include "cmsys/RegularExpression.hxx"
#include "cm_jsoncpp_value.h"
#include "cm_jsoncpp_writer.h"
#include "cm_sys_stat.h"
#include "cmAlgorithms.h"
@@ -315,21 +318,51 @@ void cmMakefile::PrintCommandTrace(const cmListFileFunction& lff) const
}
std::ostringstream msg;
msg << full_path << "(" << lff.Line << "): ";
msg << lff.Name.Original << "(";
bool expand = this->GetCMakeInstance()->GetTraceExpand();
std::vector<std::string> args;
std::string temp;
bool expand = this->GetCMakeInstance()->GetTraceExpand();
args.reserve(lff.Arguments.size());
for (cmListFileArgument const& arg : lff.Arguments) {
if (expand) {
temp = arg.Value;
this->ExpandVariablesInString(temp);
msg << temp;
args.push_back(temp);
} else {
msg << arg.Value;
args.push_back(arg.Value);
}
msg << " ";
}
msg << ")";
switch (this->GetCMakeInstance()->GetTraceFormat()) {
case cmake::TraceFormat::TRACE_JSON_V1: {
#ifndef CMAKE_BOOTSTRAP
Json::Value val;
Json::StreamWriterBuilder builder;
builder["indentation"] = "";
val["file"] = full_path;
val["line"] = static_cast<std::int64_t>(lff.Line);
val["cmd"] = lff.Name.Original;
val["args"] = Json::Value(Json::arrayValue);
for (std::string const& arg : args) {
val["args"].append(arg);
}
msg << Json::writeString(builder, val);
#endif
break;
}
case cmake::TraceFormat::TRACE_HUMAN:
msg << full_path << "(" << lff.Line << "): ";
msg << lff.Name.Original << "(";
for (std::string const& arg : args) {
msg << arg << " ";
}
msg << ")";
break;
case cmake::TraceFormat::TRACE_UNDEFINED:
msg << "INTERNAL ERROR: Trace format is TRACE_UNDEFINED";
break;
}
auto& f = this->GetCMakeInstance()->GetTraceFile();
if (f) {

View File

@@ -755,6 +755,15 @@ void cmake::SetArgs(const std::vector<std::string>& args)
std::cout << "Running with expanded trace output on.\n";
this->SetTrace(true);
this->SetTraceExpand(true);
} else if (arg.find("--trace-format=", 0) == 0) {
this->SetTrace(true);
const auto traceFormat =
StringToTraceFormat(arg.substr(strlen("--trace-format=")));
if (traceFormat == TraceFormat::TRACE_UNDEFINED) {
cmSystemTools::Error("Invalid format specified for --trace-format");
return;
}
this->SetTraceFormat(traceFormat);
} else if (arg.find("--trace-source=", 0) == 0) {
std::string file = arg.substr(strlen("--trace-source="));
cmSystemTools::ConvertToUnixSlashes(file);
@@ -895,6 +904,23 @@ cmake::LogLevel cmake::StringToLogLevel(const std::string& levelStr)
return (it != levels.cend()) ? it->second : LogLevel::LOG_UNDEFINED;
}
cmake::TraceFormat cmake::StringToTraceFormat(const std::string& traceStr)
{
using TracePair = std::pair<std::string, TraceFormat>;
static const std::vector<TracePair> levels = {
{ "human", TraceFormat::TRACE_HUMAN },
{ "json-v1", TraceFormat::TRACE_JSON_V1 },
};
const auto traceStrLowCase = cmSystemTools::LowerCase(traceStr);
const auto it = std::find_if(levels.cbegin(), levels.cend(),
[&traceStrLowCase](const TracePair& p) {
return p.first == traceStrLowCase;
});
return (it != levels.cend()) ? it->second : TraceFormat::TRACE_UNDEFINED;
}
void cmake::SetTraceFile(const std::string& file)
{
this->TraceFile.close();
@@ -909,6 +935,48 @@ void cmake::SetTraceFile(const std::string& file)
std::cout << "Trace will be written to " << file << "\n";
}
void cmake::PrintTraceFormatVersion()
{
if (!this->GetTrace()) {
return;
}
std::string msg;
switch (this->GetTraceFormat()) {
case TraceFormat::TRACE_JSON_V1: {
#ifndef CMAKE_BOOTSTRAP
Json::Value val;
Json::Value version;
Json::StreamWriterBuilder builder;
builder["indentation"] = "";
version["major"] = 1;
version["minor"] = 0;
val["version"] = version;
msg = Json::writeString(builder, val);
#endif
break;
}
case TraceFormat::TRACE_HUMAN:
msg = "";
break;
case TraceFormat::TRACE_UNDEFINED:
msg = "INTERNAL ERROR: Trace format is TRACE_UNDEFINED";
break;
}
if (msg.empty()) {
return;
}
auto& f = this->GetTraceFile();
if (f) {
f << msg << '\n';
} else {
cmSystemTools::Message(msg);
}
}
void cmake::SetDirectoriesFromFile(const std::string& arg)
{
// Check if the argument refers to a CMakeCache.txt or
@@ -1701,6 +1769,11 @@ int cmake::Run(const std::vector<std::string>& args, bool noconfigure)
return -1;
}
// Log the trace format version to the desired output
if (this->GetTrace()) {
this->PrintTraceFormatVersion();
}
// If we are given a stamp list file check if it is really out of date.
if (!this->CheckStampList.empty() &&
cmakeCheckStampList(this->CheckStampList)) {

View File

@@ -113,6 +113,14 @@ public:
LOG_TRACE
};
/** \brief Define supported trace formats **/
enum TraceFormat
{
TRACE_UNDEFINED,
TRACE_HUMAN,
TRACE_JSON_V1,
};
struct GeneratorInfo
{
std::string name;
@@ -389,6 +397,7 @@ public:
LogLevel GetLogLevel() const { return this->MessageLogLevel; }
void SetLogLevel(LogLevel level) { this->MessageLogLevel = level; }
static LogLevel StringToLogLevel(const std::string& levelStr);
static TraceFormat StringToTraceFormat(const std::string& levelStr);
bool HasCheckInProgress() const
{
@@ -418,10 +427,12 @@ public:
void SetShowLogContext(bool b) { this->LogContext = b; }
//! Do we want trace output during the cmake run.
bool GetTrace() { return this->Trace; }
bool GetTrace() const { return this->Trace; }
void SetTrace(bool b) { this->Trace = b; }
bool GetTraceExpand() { return this->TraceExpand; }
bool GetTraceExpand() const { return this->TraceExpand; }
void SetTraceExpand(bool b) { this->TraceExpand = b; }
TraceFormat GetTraceFormat() const { return this->TraceFormatVar; }
void SetTraceFormat(TraceFormat f) { this->TraceFormatVar = f; }
void AddTraceSource(std::string const& file)
{
this->TraceOnlyThisSources.push_back(file);
@@ -432,6 +443,7 @@ public:
}
cmGeneratedFileStream& GetTraceFile() { return this->TraceFile; }
void SetTraceFile(std::string const& file);
void PrintTraceFormatVersion();
bool GetWarnUninitialized() { return this->WarnUninitialized; }
void SetWarnUninitialized(bool b) { this->WarnUninitialized = b; }
@@ -579,6 +591,7 @@ private:
bool DebugOutput = false;
bool Trace = false;
bool TraceExpand = false;
TraceFormat TraceFormatVar = TRACE_HUMAN;
cmGeneratedFileStream TraceFile;
bool WarnUninitialized = false;
bool WarnUnused = false;

View File

@@ -81,6 +81,7 @@ const char* cmDocumentationOptions[][2] = {
{ "--debug-output", "Put cmake in a debug mode." },
{ "--trace", "Put cmake in trace mode." },
{ "--trace-expand", "Put cmake in trace mode with variable expansion." },
{ "--trace-format=<human|json-v1>", "Set the output format of the trace." },
{ "--trace-source=<file>",
"Trace only this CMake file/module. Multiple options allowed." },
{ "--trace-redirect=<file>",