mirror of
https://github.com/Kitware/CMake.git
synced 2026-05-06 22:30:07 -05:00
Merge topic 'traceJSON'
e113ab1168trace: Add test for the JSON-v1 trace482497e0detrace: Add JSON output format Acked-by: Kitware Robot <kwrobot@kitware.com> Merge-request: !4102
This commit is contained in:
@@ -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.
|
||||
|
||||
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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>",
|
||||
|
||||
@@ -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
@@ -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 !!! ")
|
||||
Reference in New Issue
Block a user