cmConfigureLog.cxx 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  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"
  4. #include <cassert>
  5. #include <cstdio>
  6. #include <iterator>
  7. #include <sstream>
  8. #include <utility>
  9. #include <cmext/algorithm>
  10. #include <cmext/string_view>
  11. #include <cm3p/json/writer.h>
  12. #include "cm_utf8.h"
  13. #include "cmListFileCache.h"
  14. #include "cmMakefile.h"
  15. #include "cmStringAlgorithms.h"
  16. #include "cmSystemTools.h"
  17. #include "cmake.h"
  18. cmConfigureLog::cmConfigureLog(std::string logDir,
  19. std::vector<unsigned long> logVersions)
  20. : LogDir(std::move(logDir))
  21. , LogVersions(std::move(logVersions))
  22. {
  23. // Always emit events for the latest log version.
  24. static const unsigned long LatestLogVersion = 1;
  25. if (!cm::contains(this->LogVersions, LatestLogVersion)) {
  26. this->LogVersions.emplace_back(LatestLogVersion);
  27. }
  28. Json::StreamWriterBuilder builder;
  29. this->Encoder.reset(builder.newStreamWriter());
  30. }
  31. cmConfigureLog::~cmConfigureLog()
  32. {
  33. if (this->Opened) {
  34. this->EndObject();
  35. this->Stream << "...\n";
  36. }
  37. }
  38. bool cmConfigureLog::IsAnyLogVersionEnabled(
  39. std::vector<unsigned long> const& v) const
  40. {
  41. // Both input lists are sorted. Look for a matching element.
  42. auto i1 = v.cbegin();
  43. auto i2 = this->LogVersions.cbegin();
  44. while (i1 != v.cend() && i2 != this->LogVersions.cend()) {
  45. if (*i1 < *i2) {
  46. ++i1;
  47. } else if (*i2 < *i1) {
  48. ++i2;
  49. } else {
  50. return true;
  51. }
  52. }
  53. return false;
  54. }
  55. void cmConfigureLog::WriteBacktrace(cmMakefile const& mf)
  56. {
  57. std::vector<std::string> backtrace;
  58. auto root = mf.GetCMakeInstance()->GetHomeDirectory();
  59. for (auto bt = mf.GetBacktrace(); !bt.Empty(); bt = bt.Pop()) {
  60. auto t = bt.Top();
  61. if (!t.Name.empty() || t.Line == cmListFileContext::DeferPlaceholderLine) {
  62. t.FilePath = cmSystemTools::RelativeIfUnder(root, t.FilePath);
  63. std::ostringstream s;
  64. s << t;
  65. backtrace.emplace_back(s.str());
  66. }
  67. }
  68. this->WriteValue("backtrace"_s, backtrace);
  69. }
  70. void cmConfigureLog::EnsureInit()
  71. {
  72. if (this->Opened) {
  73. return;
  74. }
  75. assert(!this->Stream.is_open());
  76. std::string name = cmStrCat(this->LogDir, "/CMakeConfigureLog.yaml");
  77. this->Stream.open(name.c_str(), std::ios::out | std::ios::app);
  78. this->Opened = true;
  79. this->Stream << "\n---\n";
  80. this->BeginObject("events"_s);
  81. }
  82. cmsys::ofstream& cmConfigureLog::BeginLine()
  83. {
  84. for (unsigned i = 0; i < this->Indent; ++i) {
  85. this->Stream << " ";
  86. }
  87. return this->Stream;
  88. }
  89. void cmConfigureLog::EndLine()
  90. {
  91. this->Stream << std::endl;
  92. }
  93. void cmConfigureLog::BeginObject(cm::string_view key)
  94. {
  95. this->BeginLine() << key << ':';
  96. this->EndLine();
  97. ++this->Indent;
  98. }
  99. void cmConfigureLog::EndObject()
  100. {
  101. assert(this->Indent);
  102. --this->Indent;
  103. }
  104. void cmConfigureLog::BeginEvent(std::string const& kind)
  105. {
  106. this->EnsureInit();
  107. this->BeginLine() << '-';
  108. this->EndLine();
  109. ++this->Indent;
  110. this->WriteValue("kind"_s, kind);
  111. }
  112. void cmConfigureLog::EndEvent()
  113. {
  114. assert(this->Indent);
  115. --this->Indent;
  116. }
  117. void cmConfigureLog::WriteValue(cm::string_view key, std::nullptr_t)
  118. {
  119. this->BeginLine() << key << ": null";
  120. this->EndLine();
  121. }
  122. void cmConfigureLog::WriteValue(cm::string_view key, bool value)
  123. {
  124. this->BeginLine() << key << ": " << (value ? "true" : "false");
  125. this->EndLine();
  126. }
  127. void cmConfigureLog::WriteValue(cm::string_view key, int value)
  128. {
  129. this->BeginLine() << key << ": " << value;
  130. this->EndLine();
  131. }
  132. void cmConfigureLog::WriteValue(cm::string_view key, std::string const& value)
  133. {
  134. this->BeginLine() << key << ": ";
  135. this->Encoder->write(value, &this->Stream);
  136. this->EndLine();
  137. }
  138. void cmConfigureLog::WriteValue(cm::string_view key,
  139. std::vector<std::string> const& list)
  140. {
  141. this->BeginObject(key);
  142. for (auto const& value : list) {
  143. this->BeginLine() << "- ";
  144. this->Encoder->write(value, &this->Stream);
  145. this->EndLine();
  146. }
  147. this->EndObject();
  148. }
  149. void cmConfigureLog::WriteLiteralTextBlock(cm::string_view key,
  150. cm::string_view text)
  151. {
  152. this->BeginLine() << key << ": |";
  153. this->EndLine();
  154. auto const l = text.length();
  155. if (l) {
  156. ++this->Indent;
  157. this->BeginLine();
  158. auto i = decltype(l){ 0 };
  159. while (i < l) {
  160. // YAML allows ' ', '\t' and "printable characters", but NOT other
  161. // ASCII whitespace; those must be escaped, as must the upper UNICODE
  162. // control characters (U+0080 - U+009F)
  163. static constexpr unsigned int C1_LAST = 0x9F;
  164. auto const c = static_cast<unsigned char>(text[i]);
  165. switch (c) {
  166. case '\r':
  167. // Print a carriage return only if it is not followed by a line feed.
  168. ++i;
  169. if (i == l || text[i] != '\n') {
  170. this->WriteEscape(c);
  171. }
  172. break;
  173. case '\n':
  174. // Print any line feeds except the very last one
  175. if (i + 1 < l) {
  176. this->EndLine();
  177. this->BeginLine();
  178. }
  179. ++i;
  180. break;
  181. case '\t':
  182. // Print horizontal tab verbatim
  183. this->Stream.put('\t');
  184. ++i;
  185. break;
  186. case '\\':
  187. // Escape backslash for disambiguation
  188. this->Stream << "\\\\";
  189. ++i;
  190. break;
  191. default:
  192. if (c >= 32 && c < 127) {
  193. // Print ascii byte.
  194. this->Stream.put(text[i]);
  195. ++i;
  196. break;
  197. } else if (c > 127) {
  198. // Decode a UTF-8 sequence.
  199. unsigned int c32;
  200. auto const* const s = text.data() + i;
  201. auto const* const e = text.data() + l;
  202. auto const* const n = cm_utf8_decode_character(s, e, &c32);
  203. if (n > s && c32 > C1_LAST) {
  204. auto const k = std::distance(s, n);
  205. this->Stream.write(s, static_cast<std::streamsize>(k));
  206. i += static_cast<unsigned>(k);
  207. break;
  208. }
  209. }
  210. // Escape non-printable byte.
  211. this->WriteEscape(c);
  212. ++i;
  213. break;
  214. }
  215. }
  216. this->EndLine();
  217. --this->Indent;
  218. }
  219. }
  220. void cmConfigureLog::WriteEscape(unsigned char c)
  221. {
  222. char buffer[6];
  223. int n = snprintf(buffer, sizeof(buffer), "\\x%02x", c);
  224. if (n > 0) {
  225. this->Stream.write(buffer, n);
  226. }
  227. }