mirror of
https://github.com/Kitware/CMake.git
synced 2026-04-22 14:23:10 -05:00
6c40e0b25e
In order to support multiple log versions without buffering the entire log, move versioning to the level of individual events. Multiple versions of an event may then be logged consecutively. Issue: #23200
262 lines
6.3 KiB
C++
262 lines
6.3 KiB
C++
/* 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/algorithm>
|
|
#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,
|
|
std::vector<unsigned long> logVersions)
|
|
: LogDir(std::move(logDir))
|
|
, LogVersions(std::move(logVersions))
|
|
{
|
|
// Always emit events for the latest log version.
|
|
static const unsigned long LatestLogVersion = 1;
|
|
if (!cm::contains(this->LogVersions, LatestLogVersion)) {
|
|
this->LogVersions.emplace_back(LatestLogVersion);
|
|
}
|
|
|
|
Json::StreamWriterBuilder builder;
|
|
this->Encoder.reset(builder.newStreamWriter());
|
|
}
|
|
|
|
cmConfigureLog::~cmConfigureLog()
|
|
{
|
|
if (this->Opened) {
|
|
this->EndObject();
|
|
this->Stream << "...\n";
|
|
}
|
|
}
|
|
|
|
bool cmConfigureLog::IsAnyLogVersionEnabled(
|
|
std::vector<unsigned long> const& v) const
|
|
{
|
|
// Both input lists are sorted. Look for a matching element.
|
|
auto i1 = v.cbegin();
|
|
auto i2 = this->LogVersions.cbegin();
|
|
while (i1 != v.cend() && i2 != this->LogVersions.cend()) {
|
|
if (*i1 < *i2) {
|
|
++i1;
|
|
} else if (*i2 < *i1) {
|
|
++i2;
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
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("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);
|
|
}
|
|
}
|