summaryrefslogtreecommitdiffstats
path: root/Source
diff options
context:
space:
mode:
authorMatthew Woehlke <matthew.woehlke@kitware.com>2022-11-23 22:02:22 (GMT)
committerBrad King <brad.king@kitware.com>2022-12-16 15:11:37 (GMT)
commit746c776caf1207049922edb3ea63586b94fca4c6 (patch)
treea846db4f6435b8d4f1c592af0a26209ba13b5a1f /Source
parente8b8d82cbf60e517ad4b9026ba24de1c59165af4 (diff)
downloadCMake-746c776caf1207049922edb3ea63586b94fca4c6.zip
CMake-746c776caf1207049922edb3ea63586b94fca4c6.tar.gz
CMake-746c776caf1207049922edb3ea63586b94fca4c6.tar.bz2
ConfigureLog: Add infrastructure for structured configure event logging
Add infrastructure for a "configure log". Use YAML for a balance of machine- and human-readability to records details of configure-time events in a structured format. Teach the RunCMake test framework to support matching the configure log. Issue: #23200
Diffstat (limited to 'Source')
-rw-r--r--Source/CMakeLists.txt2
-rw-r--r--Source/cmConfigureLog.cxx238
-rw-r--r--Source/cmConfigureLog.h61
-rw-r--r--Source/cmake.cxx12
-rw-r--r--Source/cmake.h8
5 files changed, 321 insertions, 0 deletions
diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt
index 41a901a..db928fc 100644
--- a/Source/CMakeLists.txt
+++ b/Source/CMakeLists.txt
@@ -160,6 +160,8 @@ add_library(
cmComputeLinkInformation.h
cmComputeTargetDepends.h
cmComputeTargetDepends.cxx
+ cmConfigureLog.h
+ cmConfigureLog.cxx
cmConsoleBuf.h
cmConsoleBuf.cxx
cmConstStack.h
diff --git a/Source/cmConfigureLog.cxx b/Source/cmConfigureLog.cxx
new file mode 100644
index 0000000..7489edc
--- /dev/null
+++ b/Source/cmConfigureLog.cxx
@@ -0,0 +1,238 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#include "cmConfigureLog.h"
+
+#include <cassert>
+#include <cstdio>
+#include <iterator>
+#include <sstream>
+#include <utility>
+
+#include <cmext/string_view>
+
+#include <cm3p/json/writer.h>
+
+#include "cm_utf8.h"
+
+#include "cmListFileCache.h"
+#include "cmMakefile.h"
+#include "cmStringAlgorithms.h"
+#include "cmSystemTools.h"
+#include "cmake.h"
+
+cmConfigureLog::cmConfigureLog(std::string logDir)
+ : LogDir(std::move(logDir))
+{
+ Json::StreamWriterBuilder builder;
+ this->Encoder.reset(builder.newStreamWriter());
+}
+
+cmConfigureLog::~cmConfigureLog()
+{
+ if (this->Opened) {
+ this->EndObject();
+ this->Stream << "...\n";
+ }
+}
+
+void cmConfigureLog::WriteBacktrace(cmMakefile const& mf)
+{
+ std::vector<std::string> backtrace;
+ auto root = mf.GetCMakeInstance()->GetHomeDirectory();
+ for (auto bt = mf.GetBacktrace(); !bt.Empty(); bt = bt.Pop()) {
+ auto t = bt.Top();
+ if (!t.Name.empty() || t.Line == cmListFileContext::DeferPlaceholderLine) {
+ t.FilePath = cmSystemTools::RelativeIfUnder(root, t.FilePath);
+ std::ostringstream s;
+ s << t;
+ backtrace.emplace_back(s.str());
+ }
+ }
+ this->WriteValue("backtrace"_s, backtrace);
+}
+
+void cmConfigureLog::EnsureInit()
+{
+ if (this->Opened) {
+ return;
+ }
+ assert(!this->Stream.is_open());
+
+ std::string name = cmStrCat(this->LogDir, "/CMakeConfigureLog.yaml");
+ this->Stream.open(name.c_str(), std::ios::out | std::ios::app);
+
+ this->Opened = true;
+
+ this->Stream << "\n---\n";
+ this->BeginObject("version"_s);
+ this->WriteValue("major"_s, 1);
+ this->WriteValue("minor"_s, 0);
+ this->EndObject();
+ this->BeginObject("events"_s);
+}
+
+cmsys::ofstream& cmConfigureLog::BeginLine()
+{
+ for (unsigned i = 0; i < this->Indent; ++i) {
+ this->Stream << " ";
+ }
+ return this->Stream;
+}
+
+void cmConfigureLog::EndLine()
+{
+ this->Stream << std::endl;
+}
+
+void cmConfigureLog::BeginObject(cm::string_view key)
+{
+ this->BeginLine() << key << ':';
+ this->EndLine();
+ ++this->Indent;
+}
+
+void cmConfigureLog::EndObject()
+{
+ assert(this->Indent);
+ --this->Indent;
+}
+
+void cmConfigureLog::BeginEvent(std::string const& kind)
+{
+ this->EnsureInit();
+
+ this->BeginLine() << '-';
+ this->EndLine();
+
+ ++this->Indent;
+
+ this->WriteValue("kind"_s, kind);
+}
+
+void cmConfigureLog::EndEvent()
+{
+ assert(this->Indent);
+ --this->Indent;
+}
+
+void cmConfigureLog::WriteValue(cm::string_view key, std::nullptr_t)
+{
+ this->BeginLine() << key << ": null";
+ this->EndLine();
+}
+
+void cmConfigureLog::WriteValue(cm::string_view key, bool value)
+{
+ this->BeginLine() << key << ": " << (value ? "true" : "false");
+ this->EndLine();
+}
+
+void cmConfigureLog::WriteValue(cm::string_view key, int value)
+{
+ this->BeginLine() << key << ": " << value;
+ this->EndLine();
+}
+
+void cmConfigureLog::WriteValue(cm::string_view key, std::string const& value)
+{
+ this->BeginLine() << key << ": ";
+ this->Encoder->write(value, &this->Stream);
+ this->EndLine();
+}
+
+void cmConfigureLog::WriteValue(cm::string_view key,
+ std::vector<std::string> const& list)
+{
+ this->BeginObject(key);
+ for (auto const& value : list) {
+ this->BeginLine() << "- ";
+ this->Encoder->write(value, &this->Stream);
+ this->EndLine();
+ }
+ this->EndObject();
+}
+
+void cmConfigureLog::WriteLiteralTextBlock(cm::string_view key,
+ cm::string_view text)
+{
+ this->BeginLine() << key << ": |";
+ this->EndLine();
+
+ auto const l = text.length();
+ if (l) {
+ ++this->Indent;
+ this->BeginLine();
+
+ auto i = decltype(l){ 0 };
+ while (i < l) {
+ // YAML allows ' ', '\t' and "printable characters", but NOT other
+ // ASCII whitespace; those must be escaped, as must the upper UNICODE
+ // control characters (U+0080 - U+009F)
+ static constexpr unsigned int C1_LAST = 0x9F;
+ auto const c = static_cast<unsigned char>(text[i]);
+ switch (c) {
+ case '\r':
+ // Print a carriage return only if it is not followed by a line feed.
+ ++i;
+ if (i == l || text[i] != '\n') {
+ this->WriteEscape(c);
+ }
+ break;
+ case '\n':
+ // Print any line feeds except the very last one
+ if (i + 1 < l) {
+ this->EndLine();
+ this->BeginLine();
+ }
+ ++i;
+ break;
+ case '\t':
+ // Print horizontal tab verbatim
+ this->Stream.put('\t');
+ ++i;
+ break;
+ case '\\':
+ // Escape backslash for disambiguation
+ this->Stream << "\\\\";
+ ++i;
+ break;
+ default:
+ if (c >= 32 && c < 127) {
+ // Print ascii byte.
+ this->Stream.put(text[i]);
+ ++i;
+ break;
+ } else if (c > 127) {
+ // Decode a UTF-8 sequence.
+ unsigned int c32;
+ auto const* const s = text.data() + i;
+ auto const* const e = text.data() + l;
+ auto const* const n = cm_utf8_decode_character(s, e, &c32);
+ if (n > s && c32 > C1_LAST) {
+ auto const k = std::distance(s, n);
+ this->Stream.write(s, static_cast<std::streamsize>(k));
+ i += static_cast<unsigned>(k);
+ break;
+ }
+ }
+
+ // Escape non-printable byte.
+ this->WriteEscape(c);
+ ++i;
+ break;
+ }
+ }
+
+ this->EndLine();
+ --this->Indent;
+ }
+}
+
+void cmConfigureLog::WriteEscape(unsigned char c)
+{
+ char buffer[6];
+ int n = snprintf(buffer, sizeof(buffer), "\\x%02x", c);
+ if (n > 0) {
+ this->Stream.write(buffer, n);
+ }
+}
diff --git a/Source/cmConfigureLog.h b/Source/cmConfigureLog.h
new file mode 100644
index 0000000..a71fd0c
--- /dev/null
+++ b/Source/cmConfigureLog.h
@@ -0,0 +1,61 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#pragma once
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <cm/string_view>
+
+#include "cmsys/FStream.hxx"
+
+namespace Json {
+class StreamWriter;
+}
+
+class cmMakefile;
+
+class cmConfigureLog
+{
+public:
+ cmConfigureLog(std::string logDir);
+ ~cmConfigureLog();
+
+ void WriteBacktrace(cmMakefile const& mf);
+
+ void EnsureInit();
+
+ void BeginEvent(std::string const& kind);
+ void EndEvent();
+
+ void BeginObject(cm::string_view key);
+ void EndObject();
+
+ // TODO other value types
+ void WriteValue(cm::string_view key, std::nullptr_t);
+ void WriteValue(cm::string_view key, bool value);
+ void WriteValue(cm::string_view key, int value);
+ void WriteValue(cm::string_view key, std::string const& value);
+ void WriteValue(cm::string_view key, std::vector<std::string> const& list);
+
+ void WriteTextBlock(cm::string_view key, cm::string_view text);
+ void WriteLiteralTextBlock(cm::string_view key, cm::string_view text);
+
+ void WriteLiteralTextBlock(cm::string_view key, std::string const& text)
+ {
+ this->WriteLiteralTextBlock(key, cm::string_view{ text });
+ }
+
+private:
+ std::string LogDir;
+ cmsys::ofstream Stream;
+ unsigned Indent = 0;
+ bool Opened = false;
+
+ std::unique_ptr<Json::StreamWriter> Encoder;
+
+ cmsys::ofstream& BeginLine();
+ void EndLine();
+ void WriteEscape(unsigned char c);
+};
diff --git a/Source/cmake.cxx b/Source/cmake.cxx
index befcb55..17027bc 100644
--- a/Source/cmake.cxx
+++ b/Source/cmake.cxx
@@ -70,6 +70,7 @@
# include <cm3p/curl/curl.h>
# include <cm3p/json/writer.h>
+# include "cmConfigureLog.h"
# include "cmFileAPI.h"
# include "cmGraphVizWriter.h"
# include "cmVariableWatch.h"
@@ -2423,10 +2424,21 @@ int cmake::ActualConfigure()
#if !defined(CMAKE_BOOTSTRAP)
this->FileAPI = cm::make_unique<cmFileAPI>(this);
this->FileAPI->ReadQueries();
+
+ if (!this->GetIsInTryCompile()) {
+ this->TruncateOutputLog("CMakeConfigureLog.yaml");
+ this->ConfigureLog = cm::make_unique<cmConfigureLog>(
+ cmStrCat(this->GetHomeOutputDirectory(), "/CMakeFiles"_s));
+ }
#endif
// actually do the configure
this->GlobalGenerator->Configure();
+
+#if !defined(CMAKE_BOOTSTRAP)
+ this->ConfigureLog.reset();
+#endif
+
// Before saving the cache
// if the project did not define one of the entries below, add them now
// so users can edit the values in the cache:
diff --git a/Source/cmake.h b/Source/cmake.h
index 325c0c5..10db87d 100644
--- a/Source/cmake.h
+++ b/Source/cmake.h
@@ -37,6 +37,7 @@
# include "cmMakefileProfilingData.h"
#endif
+class cmConfigureLog;
class cmExternalMakefileProjectGeneratorFactory;
class cmFileAPI;
class cmFileTimeCache;
@@ -521,6 +522,10 @@ public:
void SetTraceFile(std::string const& file);
void PrintTraceFormatVersion();
+#ifndef CMAKE_BOOTSTRAP
+ cmConfigureLog* GetConfigureLog() const { return this->ConfigureLog.get(); }
+#endif
+
//! Use trace from another ::cmake instance.
void SetTraceRedirect(cmake* other);
@@ -714,6 +719,9 @@ private:
TraceFormat TraceFormatVar = TRACE_HUMAN;
cmGeneratedFileStream TraceFile;
cmake* TraceRedirect = nullptr;
+#ifndef CMAKE_BOOTSTRAP
+ std::unique_ptr<cmConfigureLog> ConfigureLog;
+#endif
bool WarnUninitialized = false;
bool WarnUnusedCli = true;
bool CheckSystemVars = false;