CMake/Source/cmMessenger.cxx
Matthew Woehlke dcaa52c6c3 PrintCallStack: "Fix" entry suppression
Backtraces may contain non-specific entries (n.b. FromListFilePath in
cmListFileContext). Our prior expectation is that these will ALWAYS
replaced by more specific entries. However, with the previous change to
allow users to create arbitrary stack entries, this is no longer the
case. Modify PrintCallStack to only skip printing these non-specific
entries if they were in fact preceded by a more specific entry.

This, and the functionality of the preceding commit, is intended to be
used for CPS dependency resolution. Because said resolution may fail at
a non-trivial depth, it is important to be able to provide the user with
the path of packages that led to the failure. However, because this
happens through parsing JSON, there is no listfile function to associate
with the stack entries, nor do we attempt to keep track of line numbers
from the JSON. As a result, these entries will never be more specific
than the name of the CPS file whose dependencies we are trying to
locate.
2024-12-07 09:12:54 -05:00

247 lines
6.6 KiB
C++

/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file Copyright.txt or https://cmake.org/licensing for details. */
#include "cmMessenger.h"
#include "cmDocumentationFormatter.h"
#include "cmMessageMetadata.h"
#include "cmMessageType.h"
#include "cmStringAlgorithms.h"
#include "cmSystemTools.h"
#if !defined(CMAKE_BOOTSTRAP)
# include "cmsys/SystemInformation.hxx"
#endif
#include <sstream>
#include <utility>
#include "cmsys/Terminal.h"
#ifdef CMake_ENABLE_DEBUGGER
# include "cmDebuggerAdapter.h"
#endif
namespace {
const char* getMessageTypeStr(MessageType t)
{
switch (t) {
case MessageType::FATAL_ERROR:
return "Error";
case MessageType::INTERNAL_ERROR:
return "Internal Error (please report a bug)";
case MessageType::LOG:
return "Debug Log";
case MessageType::DEPRECATION_ERROR:
return "Deprecation Error";
case MessageType::DEPRECATION_WARNING:
return "Deprecation Warning";
case MessageType::AUTHOR_WARNING:
return "Warning (dev)";
case MessageType::AUTHOR_ERROR:
return "Error (dev)";
default:
break;
}
return "Warning";
}
int getMessageColor(MessageType t)
{
switch (t) {
case MessageType::INTERNAL_ERROR:
case MessageType::FATAL_ERROR:
case MessageType::AUTHOR_ERROR:
return cmsysTerminal_Color_ForegroundRed;
case MessageType::AUTHOR_WARNING:
case MessageType::WARNING:
return cmsysTerminal_Color_ForegroundYellow;
default:
return cmsysTerminal_Color_Normal;
}
}
void printMessageText(std::ostream& msg, std::string const& text)
{
msg << ":\n";
cmDocumentationFormatter formatter;
formatter.SetIndent(2u);
formatter.PrintFormatted(msg, text);
}
void displayMessage(MessageType t, std::ostringstream& msg)
{
// Add a note about warning suppression.
if (t == MessageType::AUTHOR_WARNING) {
msg << "This warning is for project developers. Use -Wno-dev to suppress "
"it.";
} else if (t == MessageType::AUTHOR_ERROR) {
msg << "This error is for project developers. Use -Wno-error=dev to "
"suppress it.";
}
// Add a terminating blank line.
msg << '\n';
#if !defined(CMAKE_BOOTSTRAP)
// Add a C++ stack trace to internal errors.
if (t == MessageType::INTERNAL_ERROR) {
std::string stack = cmsys::SystemInformation::GetProgramStack(0, 0);
if (!stack.empty()) {
if (cmHasLiteralPrefix(stack, "WARNING:")) {
stack = "Note:" + stack.substr(8);
}
msg << stack << '\n';
}
}
#endif
// Output the message.
cmMessageMetadata md;
md.desiredColor = getMessageColor(t);
if (t == MessageType::FATAL_ERROR || t == MessageType::INTERNAL_ERROR ||
t == MessageType::DEPRECATION_ERROR || t == MessageType::AUTHOR_ERROR) {
cmSystemTools::SetErrorOccurred();
md.title = "Error";
} else {
md.title = "Warning";
}
cmSystemTools::Message(msg.str(), md);
}
void PrintCallStack(std::ostream& out, cmListFileBacktrace bt,
cm::optional<std::string> const& topSource)
{
// The call stack exists only if we have at least two calls on top
// of the bottom.
if (bt.Empty()) {
return;
}
std::string lastFilePath = bt.Top().FilePath;
bt = bt.Pop();
if (bt.Empty()) {
return;
}
bool first = true;
for (; !bt.Empty(); bt = bt.Pop()) {
cmListFileContext lfc = bt.Top();
if (lfc.Name.empty() &&
lfc.Line != cmListFileContext::DeferPlaceholderLine &&
lfc.FilePath == lastFilePath) {
// An entry with no function name is frequently preceded (in the stack)
// by a more specific entry. When this happens (as verified by the
// preceding entry referencing the same file path), skip the less
// specific entry, as we have already printed the more specific one.
continue;
}
if (first) {
first = false;
out << "Call Stack (most recent call first):\n";
}
lastFilePath = lfc.FilePath;
if (topSource) {
lfc.FilePath = cmSystemTools::RelativeIfUnder(*topSource, lfc.FilePath);
}
out << " " << lfc << '\n';
}
}
} // anonymous namespace
MessageType cmMessenger::ConvertMessageType(MessageType t) const
{
if (t == MessageType::AUTHOR_WARNING || t == MessageType::AUTHOR_ERROR) {
if (this->GetDevWarningsAsErrors()) {
return MessageType::AUTHOR_ERROR;
}
return MessageType::AUTHOR_WARNING;
}
if (t == MessageType::DEPRECATION_WARNING ||
t == MessageType::DEPRECATION_ERROR) {
if (this->GetDeprecatedWarningsAsErrors()) {
return MessageType::DEPRECATION_ERROR;
}
return MessageType::DEPRECATION_WARNING;
}
return t;
}
bool cmMessenger::IsMessageTypeVisible(MessageType t) const
{
if (t == MessageType::DEPRECATION_ERROR) {
return this->GetDeprecatedWarningsAsErrors();
}
if (t == MessageType::DEPRECATION_WARNING) {
return !this->GetSuppressDeprecatedWarnings();
}
if (t == MessageType::AUTHOR_ERROR) {
return this->GetDevWarningsAsErrors();
}
if (t == MessageType::AUTHOR_WARNING) {
return !this->GetSuppressDevWarnings();
}
return true;
}
void cmMessenger::IssueMessage(MessageType t, const std::string& text,
const cmListFileBacktrace& backtrace) const
{
bool force = false;
// override the message type, if needed, for warnings and errors
MessageType override = this->ConvertMessageType(t);
if (override != t) {
t = override;
force = true;
}
if (force || this->IsMessageTypeVisible(t)) {
this->DisplayMessage(t, text, backtrace);
}
}
void cmMessenger::DisplayMessage(MessageType t, const std::string& text,
const cmListFileBacktrace& backtrace) const
{
std::ostringstream msg;
// Print the message preamble.
msg << "CMake " << getMessageTypeStr(t);
// Add the immediate context.
this->PrintBacktraceTitle(msg, backtrace);
printMessageText(msg, text);
// Add the rest of the context.
PrintCallStack(msg, backtrace, this->TopSource);
displayMessage(t, msg);
#ifdef CMake_ENABLE_DEBUGGER
if (DebuggerAdapter) {
DebuggerAdapter->OnMessageOutput(t, msg.str());
}
#endif
}
void cmMessenger::PrintBacktraceTitle(std::ostream& out,
cmListFileBacktrace const& bt) const
{
// The title exists only if we have a call on top of the bottom.
if (bt.Empty()) {
return;
}
cmListFileContext lfc = bt.Top();
if (this->TopSource) {
lfc.FilePath =
cmSystemTools::RelativeIfUnder(*this->TopSource, lfc.FilePath);
}
out << (lfc.Line ? " at " : " in ") << lfc;
}
void cmMessenger::SetTopSource(cm::optional<std::string> topSource)
{
this->TopSource = std::move(topSource);
}