Merge topic 'traceJSON'

e113ab1168 trace: Add test for the JSON-v1 trace
482497e0de trace: Add JSON output format

Acked-by: Kitware Robot <kwrobot@kitware.com>
Merge-request: !4102
This commit is contained in:
Kyle Edwards
2019-12-27 15:52:47 +00:00
committed by Kitware Robot
13 changed files with 300 additions and 10 deletions
+60
View File
@@ -257,6 +257,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.
+7
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.
+40 -7
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) {
+73
View File
@@ -758,6 +758,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);
@@ -898,6 +907,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();
@@ -912,6 +938,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
@@ -1704,6 +1772,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)) {
+15 -2
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
{
@@ -422,10 +431,12 @@ public:
void SetDebugFindOutputOn(bool b) { this->DebugFindOutput = 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);
@@ -436,6 +447,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; }
@@ -584,6 +596,7 @@ private:
bool DebugFindOutput = false;
bool Trace = false;
bool TraceExpand = false;
TraceFormat TraceFormatVar = TRACE_HUMAN;
cmGeneratedFileStream TraceFile;
bool WarnUninitialized = false;
bool WarnUnused = false;
+1
View File
@@ -82,6 +82,7 @@ const char* cmDocumentationOptions[][2] = {
{ "--debug-find", "Put cmake find 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>",
+1 -1
View File
@@ -454,7 +454,7 @@ add_RunCMake_test(target_include_directories)
add_RunCMake_test(target_sources)
add_RunCMake_test(CheckModules)
add_RunCMake_test(CheckIPOSupported)
add_RunCMake_test(CommandLine -DCMAKE_SYSTEM_NAME=${CMAKE_SYSTEM_NAME} -DCYGWIN=${CYGWIN})
add_RunCMake_test(CommandLine -DCMAKE_SYSTEM_NAME=${CMAKE_SYSTEM_NAME} -DCYGWIN=${CYGWIN} -DPYTHON_EXECUTABLE=${PYTHON_EXECUTABLE})
add_RunCMake_test(CommandLineTar)
if(CMAKE_PLATFORM_NO_VERSIONED_SONAME OR (NOT CMAKE_SHARED_LIBRARY_SONAME_FLAG AND NOT CMAKE_SHARED_LIBRARY_SONAME_C_FLAG))
@@ -607,6 +607,14 @@ set(RunCMake_TEST_OPTIONS --trace-redirect=/no/such/file.txt)
run_cmake(trace-redirect-nofile)
unset(RunCMake_TEST_OPTIONS)
set(RunCMake_TEST_OPTIONS --trace --trace-format=json-v1 --trace-redirect=${RunCMake_BINARY_DIR}/json-v1.trace)
run_cmake(trace-json-v1)
unset(RunCMake_TEST_OPTIONS)
set(RunCMake_TEST_OPTIONS --trace-expand --trace-format=json-v1 --trace-redirect=${RunCMake_BINARY_DIR}/json-v1-expand.trace)
run_cmake(trace-json-v1-expand)
unset(RunCMake_TEST_OPTIONS)
set(RunCMake_TEST_OPTIONS -Wno-deprecated --warn-uninitialized)
run_cmake(warn-uninitialized)
unset(RunCMake_TEST_OPTIONS)
@@ -0,0 +1,11 @@
if(PYTHON_EXECUTABLE)
execute_process(
COMMAND ${PYTHON_EXECUTABLE} "${RunCMake_SOURCE_DIR}/trace-json-v1-check.py" "${RunCMake_BINARY_DIR}/json-v1.trace"
RESULT_VARIABLE result
OUTPUT_VARIABLE output
ERROR_VARIABLE output
)
if(NOT result EQUAL 0)
set(RunCMake_TEST_FAILED "JSON trace validation failed:\n${output}")
endif()
endif()
+67
View File
@@ -0,0 +1,67 @@
#!/usr/bin/env python3
import argparse
import json
import os
import sys
if sys.version_info[0] >= 3:
unicode = str
parser = argparse.ArgumentParser(description='Checks the trace output')
parser.add_argument('-e', '--expand', action='store_true')
parser.add_argument('trace', type=str, help='the trace file to check')
args = parser.parse_args()
assert os.path.exists(args.trace)
if args.expand:
msg_args = ['STATUS', 'fff', 'fff;sss; SPACES !!! ', ' 42 space in string!', ' SPACES !!! ']
else:
msg_args = ['STATUS', 'fff', '${ASDF}', ' ${FOO} ${BAR}', ' SPACES !!! ']
required_traces = [
{
'args': ['STATUS', 'JSON-V1 str', 'spaces'],
'cmd': 'message',
},
{
'args': ['ASDF', 'fff', 'sss', ' SPACES !!! '],
'cmd': 'set',
},
{
'args': ['FOO', '42'],
'cmd': 'set',
},
{
'args': ['BAR', ' space in string!'],
'cmd': 'set',
},
{
'args': msg_args,
'cmd': 'message',
},
]
with open(args.trace, 'r') as fp:
# Check for version (must be the first document)
vers = json.loads(fp.readline())
assert sorted(vers.keys()) == ['version']
assert sorted(vers['version'].keys()) == ['major', 'minor']
assert vers['version']['major'] == 1
assert vers['version']['minor'] == 0
for i in fp.readlines():
line = json.loads(i)
assert sorted(line.keys()) == ['args', 'cmd', 'file', 'line']
assert isinstance(line['args'], list)
assert isinstance(line['cmd'], unicode)
assert isinstance(line['file'], unicode)
assert isinstance(line['line'], int)
for i in required_traces:
if i['cmd'] == line['cmd'] and i['args'] == line['args']:
i['found'] = True
assert all([x.get('found', False) == True for x in required_traces])
@@ -0,0 +1,11 @@
if(PYTHON_EXECUTABLE)
execute_process(
COMMAND ${PYTHON_EXECUTABLE} "${RunCMake_SOURCE_DIR}/trace-json-v1-check.py" --expand "${RunCMake_BINARY_DIR}/json-v1-expand.trace"
RESULT_VARIABLE result
OUTPUT_VARIABLE output
ERROR_VARIABLE output
)
if(NOT result EQUAL 0)
set(RunCMake_TEST_FAILED "JSON trace validation failed:\n${output}")
endif()
endif()
@@ -0,0 +1 @@
include(trace-json-v1.cmake)
@@ -0,0 +1,5 @@
message(STATUS "JSON-V1 str" "spaces")
set(ASDF fff sss " SPACES !!! ")
set(FOO 42)
set(BAR " space in string!")
message(STATUS fff ${ASDF} " ${FOO} ${BAR}" " SPACES !!! ")