1 /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
2 file Copyright.txt or https://cmake.org/licensing for details. */
3 #include "cmConfigureLog.h"
11 #include <cmext/algorithm>
12 #include <cmext/string_view>
14 #include <cm3p/json/writer.h>
18 #include "cmListFileCache.h"
19 #include "cmMakefile.h"
21 #include "cmStringAlgorithms.h"
22 #include "cmSystemTools.h"
25 cmConfigureLog::cmConfigureLog(std::string logDir
,
26 std::vector
<unsigned long> logVersions
)
27 : LogDir(std::move(logDir
))
28 , LogVersions(std::move(logVersions
))
30 // Always emit events for the latest log version.
31 static const unsigned long LatestLogVersion
= 1;
32 if (!cm::contains(this->LogVersions
, LatestLogVersion
)) {
33 this->LogVersions
.emplace_back(LatestLogVersion
);
36 Json::StreamWriterBuilder builder
;
37 this->Encoder
.reset(builder
.newStreamWriter());
40 cmConfigureLog::~cmConfigureLog()
44 this->Stream
<< "...\n";
48 bool cmConfigureLog::IsAnyLogVersionEnabled(
49 std::vector
<unsigned long> const& v
) const
51 // Both input lists are sorted. Look for a matching element.
53 auto i2
= this->LogVersions
.cbegin();
54 while (i1
!= v
.cend() && i2
!= this->LogVersions
.cend()) {
57 } else if (*i2
< *i1
) {
66 void cmConfigureLog::WriteBacktrace(cmMakefile
const& mf
)
68 std::vector
<std::string
> backtrace
;
69 auto root
= mf
.GetCMakeInstance()->GetHomeDirectory();
70 for (auto bt
= mf
.GetBacktrace(); !bt
.Empty(); bt
= bt
.Pop()) {
72 if (!t
.Name
.empty() || t
.Line
== cmListFileContext::DeferPlaceholderLine
) {
73 t
.FilePath
= cmSystemTools::RelativeIfUnder(root
, t
.FilePath
);
76 backtrace
.emplace_back(s
.str());
79 this->WriteValue("backtrace"_s
, backtrace
);
82 void cmConfigureLog::WriteChecks(cmMakefile
const& mf
)
84 if (!mf
.GetCMakeInstance()->HasCheckInProgress()) {
87 this->BeginObject("checks"_s
);
88 for (auto const& value
:
89 cmReverseRange(mf
.GetCMakeInstance()->GetCheckInProgressMessages())) {
90 this->BeginLine() << "- ";
91 this->Encoder
->write(value
, &this->Stream
);
97 void cmConfigureLog::EnsureInit()
102 assert(!this->Stream
.is_open());
104 std::string name
= cmStrCat(this->LogDir
, "/CMakeConfigureLog.yaml");
105 this->Stream
.open(name
.c_str(), std::ios::out
| std::ios::app
);
109 this->Stream
<< "\n---\n";
110 this->BeginObject("events"_s
);
113 cmsys::ofstream
& cmConfigureLog::BeginLine()
115 for (unsigned i
= 0; i
< this->Indent
; ++i
) {
121 void cmConfigureLog::EndLine()
123 this->Stream
<< std::endl
;
126 void cmConfigureLog::BeginObject(cm::string_view key
)
128 this->BeginLine() << key
<< ':';
133 void cmConfigureLog::EndObject()
135 assert(this->Indent
);
139 void cmConfigureLog::BeginEvent(std::string
const& kind
, cmMakefile
const& mf
)
143 this->BeginLine() << '-';
148 this->WriteValue("kind"_s
, kind
);
149 this->WriteBacktrace(mf
);
150 this->WriteChecks(mf
);
153 void cmConfigureLog::EndEvent()
155 assert(this->Indent
);
159 void cmConfigureLog::WriteValue(cm::string_view key
, std::nullptr_t
)
161 this->BeginLine() << key
<< ": null";
165 void cmConfigureLog::WriteValue(cm::string_view key
, bool value
)
167 this->BeginLine() << key
<< ": " << (value
? "true" : "false");
171 void cmConfigureLog::WriteValue(cm::string_view key
, int value
)
173 this->BeginLine() << key
<< ": " << value
;
177 void cmConfigureLog::WriteValue(cm::string_view key
, std::string
const& value
)
179 this->BeginLine() << key
<< ": ";
180 this->Encoder
->write(value
, &this->Stream
);
184 void cmConfigureLog::WriteValue(cm::string_view key
,
185 std::vector
<std::string
> const& list
)
187 this->BeginObject(key
);
188 for (auto const& value
: list
) {
189 this->BeginLine() << "- ";
190 this->Encoder
->write(value
, &this->Stream
);
196 void cmConfigureLog::WriteValue(cm::string_view key
,
197 std::map
<std::string
, std::string
> const& map
)
199 static const std::string rawKeyChars
= //
200 "ABCDEFGHIJKLMNOPQRSTUVWXYZ" //
201 "abcdefghijklmnopqrstuvwxyz" //
205 this->BeginObject(key
);
206 for (auto const& entry
: map
) {
207 if (entry
.first
.find_first_not_of(rawKeyChars
) == std::string::npos
) {
208 this->WriteValue(entry
.first
, entry
.second
);
211 this->Encoder
->write(entry
.first
, &this->Stream
);
212 this->Stream
<< ": ";
213 this->Encoder
->write(entry
.second
, &this->Stream
);
220 void cmConfigureLog::WriteLiteralTextBlock(cm::string_view key
,
221 cm::string_view text
)
223 this->BeginLine() << key
<< ": |";
226 auto const l
= text
.length();
231 auto i
= decltype(l
){ 0 };
233 // YAML allows ' ', '\t' and "printable characters", but NOT other
234 // ASCII whitespace; those must be escaped, as must the upper UNICODE
235 // control characters (U+0080 - U+009F)
236 static constexpr unsigned int C1_LAST
= 0x9F;
237 auto const c
= static_cast<unsigned char>(text
[i
]);
240 // Print a carriage return only if it is not followed by a line feed.
242 if (i
== l
|| text
[i
] != '\n') {
243 this->WriteEscape(c
);
247 // Print any line feeds except the very last one
255 // Print horizontal tab verbatim
256 this->Stream
.put('\t');
260 // Escape backslash for disambiguation
261 this->Stream
<< "\\\\";
265 if (c
>= 32 && c
< 127) {
267 this->Stream
.put(text
[i
]);
270 } else if (c
> 127) {
271 // Decode a UTF-8 sequence.
273 auto const* const s
= text
.data() + i
;
274 auto const* const e
= text
.data() + l
;
275 auto const* const n
= cm_utf8_decode_character(s
, e
, &c32
);
276 if (n
> s
&& c32
> C1_LAST
) {
277 auto const k
= std::distance(s
, n
);
278 this->Stream
.write(s
, static_cast<std::streamsize
>(k
));
279 i
+= static_cast<unsigned>(k
);
284 // Escape non-printable byte.
285 this->WriteEscape(c
);
296 void cmConfigureLog::WriteEscape(unsigned char c
)
299 int n
= snprintf(buffer
, sizeof(buffer
), "\\x%02x", c
);
301 this->Stream
.write(buffer
, n
);