1
0
Эх сурвалжийг харах

Merge topic 'traceJSON'

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

Acked-by: Kitware Robot <[email protected]>
Merge-request: !4102
Kyle Edwards 6 жил өмнө
parent
commit
bb811568cc

+ 60 - 0
Help/manual/cmake.1.rst

@@ -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 - 0
Help/release/dev/json_trace.rst

@@ -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
Source/cmMakefile.cxx

@@ -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);
+    }
+  }
+
+  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;
     }
-    msg << " ";
+    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;
   }
-  msg << ")";
 
   auto& f = this->GetCMakeInstance()->GetTraceFile();
   if (f) {

+ 73 - 0
Source/cmake.cxx

@@ -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
Source/cmake.h

@@ -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 - 0
Source/cmakemain.cxx

@@ -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
Tests/RunCMake/CMakeLists.txt

@@ -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))

+ 8 - 0
Tests/RunCMake/CommandLine/RunCMakeTest.cmake

@@ -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)

+ 11 - 0
Tests/RunCMake/CommandLine/trace-json-v1-check.cmake

@@ -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
Tests/RunCMake/CommandLine/trace-json-v1-check.py

@@ -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])

+ 11 - 0
Tests/RunCMake/CommandLine/trace-json-v1-expand-check.cmake

@@ -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()

+ 1 - 0
Tests/RunCMake/CommandLine/trace-json-v1-expand.cmake

@@ -0,0 +1 @@
+include(trace-json-v1.cmake)

+ 5 - 0
Tests/RunCMake/CommandLine/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 !!!  ")