Files
CMake/Source/cmMessageCommand.cxx
Matthew Woehlke 72e7305c3e Diagnostics: Handle CMAKE_{WARN,ERROR}_DEPRECATED
Update documentation for aforementioned variables, as their use is
deprecated under the new diagnostic system. Introduce a policy which
reintroduces their use to control specific deprecation diagnostics if
the policy is not NEW.

Note that this means there is NO synchronization between the variables
and the new diagnostic state.
2026-05-07 11:07:27 -04:00

291 lines
8.3 KiB
C++

/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file LICENSE.rst or https://cmake.org/licensing for details. */
#include "cmMessageCommand.h"
#include <cassert>
#include <utility>
#include <cm/memory>
#include <cm/string_view>
#include <cmext/string_view>
#include "cmConfigureLog.h"
#include "cmDiagnostics.h"
#include "cmExecutionStatus.h"
#include "cmList.h"
#include "cmMakefile.h"
#include "cmMessageType.h"
#include "cmMessenger.h"
#include "cmPolicies.h"
#include "cmRange.h"
#include "cmStringAlgorithms.h"
#include "cmSystemTools.h"
#include "cmValue.h"
#include "cmake.h"
#ifdef CMake_ENABLE_DEBUGGER
# include "cmDebuggerAdapter.h"
#endif
namespace {
std::string IndentText(std::string text, cmMakefile& mf)
{
auto indent =
cmList{ mf.GetSafeDefinition("CMAKE_MESSAGE_INDENT") }.join("");
auto const showContext = mf.GetCMakeInstance()->GetShowLogContext() ||
mf.IsOn("CMAKE_MESSAGE_CONTEXT_SHOW");
if (showContext) {
auto context =
cmList{ mf.GetSafeDefinition("CMAKE_MESSAGE_CONTEXT") }.join(".");
if (!context.empty()) {
indent.insert(0u, cmStrCat("["_s, context, "] "_s));
}
}
if (!indent.empty()) {
cmSystemTools::ReplaceString(text, "\n", "\n" + indent);
text.insert(0u, indent);
}
return text;
}
void ReportCheckResult(cm::string_view what, std::string result,
cmMakefile& mf)
{
if (mf.GetCMakeInstance()->HasCheckInProgress()) {
auto text = mf.GetCMakeInstance()->GetTopCheckInProgressMessage() + " - " +
std::move(result);
mf.DisplayStatus(IndentText(std::move(text), mf), -1);
} else {
mf.GetMessenger()->DisplayMessage(
MessageType::WARNING, cmDiagnostics::CMD_AUTHOR,
cmStrCat("Ignored "_s, what, " without CHECK_START"_s),
mf.GetBacktrace());
}
}
namespace {
#ifndef CMAKE_BOOTSTRAP
void WriteMessageEvent(cmConfigureLog& log, cmMakefile const& mf,
std::string const& message)
{
// Keep in sync with cmFileAPIConfigureLog's DumpEventKindNames.
static std::vector<unsigned int> const LogVersionsWithMessageV1{ 1 };
if (log.IsAnyLogVersionEnabled(LogVersionsWithMessageV1)) {
log.BeginEvent("message-v1", mf);
log.WriteLiteralTextBlock("message"_s, message);
log.EndEvent();
}
}
#endif
}
} // anonymous namespace
// cmLibraryCommand
bool cmMessageCommand(std::vector<std::string> const& args,
cmExecutionStatus& status)
{
if (args.empty()) {
status.SetError("called with incorrect number of arguments");
return false;
}
auto& mf = status.GetMakefile();
auto i = args.cbegin();
std::unique_ptr<cmMakefile::DiagnosticPushPop> ds;
auto category = cmDiagnostics::CMD_NONE;
auto type = MessageType::MESSAGE;
auto fatal = false;
auto level = Message::LogLevel::LOG_UNDEFINED;
auto checkingType = Message::CheckType::UNDEFINED;
if (*i == "SEND_ERROR") {
type = MessageType::FATAL_ERROR;
level = Message::LogLevel::LOG_ERROR;
++i;
} else if (*i == "FATAL_ERROR") {
fatal = true;
type = MessageType::FATAL_ERROR;
level = Message::LogLevel::LOG_ERROR;
++i;
} else if (*i == "WARNING") {
type = MessageType::WARNING;
level = Message::LogLevel::LOG_WARNING;
++i;
} else if (*i == "AUTHOR_WARNING") {
category = cmDiagnostics::CMD_AUTHOR;
switch (mf.GetDiagnosticAction(category)) {
case cmDiagnostics::Ignore:
return true;
case cmDiagnostics::FatalError:
fatal = true;
CM_FALLTHROUGH;
case cmDiagnostics::SendError:
type = MessageType::FATAL_ERROR;
level = Message::LogLevel::LOG_ERROR;
break;
default:
type = MessageType::WARNING;
level = Message::LogLevel::LOG_WARNING;
break;
}
++i;
} else if (*i == "CHECK_START") {
level = Message::LogLevel::LOG_STATUS;
checkingType = Message::CheckType::CHECK_START;
++i;
} else if (*i == "CHECK_PASS") {
level = Message::LogLevel::LOG_STATUS;
checkingType = Message::CheckType::CHECK_PASS;
++i;
} else if (*i == "CHECK_FAIL") {
level = Message::LogLevel::LOG_STATUS;
checkingType = Message::CheckType::CHECK_FAIL;
++i;
} else if (*i == "CONFIGURE_LOG") {
#ifndef CMAKE_BOOTSTRAP
if (cmConfigureLog* log = mf.GetCMakeInstance()->GetConfigureLog()) {
++i;
WriteMessageEvent(*log, mf, cmJoin(cmMakeRange(i, args.cend()), ""_s));
}
#endif
return true;
} else if (*i == "STATUS") {
level = Message::LogLevel::LOG_STATUS;
++i;
} else if (*i == "VERBOSE") {
level = Message::LogLevel::LOG_VERBOSE;
++i;
} else if (*i == "DEBUG") {
level = Message::LogLevel::LOG_DEBUG;
++i;
} else if (*i == "TRACE") {
level = Message::LogLevel::LOG_TRACE;
++i;
} else if (*i == "DEPRECATION") {
category = cmDiagnostics::CMD_DEPRECATED;
cmPolicies::PolicyStatus const cmp0218 =
mf.GetPolicyStatus(cmPolicies::CMP0218);
if (cmp0218 != cmPolicies::NEW) {
std::unique_ptr<cmMakefile::PolicyPushPop> ps;
if (cmp0218 != cmPolicies::OLD) {
// Suppress warnings about using old variables.
ps = cm::make_unique<cmMakefile::PolicyPushPop>(&mf);
mf.SetPolicy(cmPolicies::CMP0218, cmPolicies::OLD);
}
ds = cm::make_unique<cmMakefile::DiagnosticPushPop>(&mf);
// Use old variables to determine diagnostic action.
if (mf.IsOn("CMAKE_ERROR_DEPRECATED")) {
mf.SetDiagnostic(category, cmDiagnostics::FatalError);
} else {
cmValue const warn = mf.GetDefinition("CMAKE_WARN_DEPRECATED");
if (warn.IsSet() && !warn.IsOn()) {
mf.SetDiagnostic(category, cmDiagnostics::Ignore);
} else {
mf.SetDiagnostic(category, cmDiagnostics::Warn);
}
}
}
switch (mf.GetDiagnosticAction(category)) {
case cmDiagnostics::Ignore:
return true;
case cmDiagnostics::FatalError:
fatal = true;
CM_FALLTHROUGH;
case cmDiagnostics::SendError:
type = MessageType::FATAL_ERROR;
level = Message::LogLevel::LOG_ERROR;
break;
default:
type = MessageType::WARNING;
level = Message::LogLevel::LOG_WARNING;
break;
}
++i;
} else if (*i == "NOTICE") {
// `NOTICE` message type is going to be output to stderr
level = Message::LogLevel::LOG_NOTICE;
++i;
} else {
// Messages w/o any type are `NOTICE`s
level = Message::LogLevel::LOG_NOTICE;
}
assert("Message log level expected to be set" &&
level != Message::LogLevel::LOG_UNDEFINED);
Message::LogLevel desiredLevel = mf.GetCurrentLogLevel();
if (desiredLevel < level) {
// Suppress the message
return true;
}
auto message = cmJoin(cmMakeRange(i, args.cend()), "");
switch (level) {
case Message::LogLevel::LOG_ERROR:
case Message::LogLevel::LOG_WARNING:
// we've overridden the message type, above, so display it directly
mf.GetMessenger()->DisplayMessage(type, category, message,
mf.GetBacktrace());
break;
case Message::LogLevel::LOG_NOTICE:
cmSystemTools::Message(IndentText(message, mf));
#ifdef CMake_ENABLE_DEBUGGER
if (mf.GetCMakeInstance()->GetDebugAdapter()) {
mf.GetCMakeInstance()->GetDebugAdapter()->OnMessageOutput(type,
message);
}
#endif
break;
case Message::LogLevel::LOG_STATUS:
switch (checkingType) {
case Message::CheckType::CHECK_START:
mf.DisplayStatus(IndentText(message, mf), -1);
mf.GetCMakeInstance()->PushCheckInProgressMessage(message);
break;
case Message::CheckType::CHECK_PASS:
ReportCheckResult("CHECK_PASS"_s, message, mf);
break;
case Message::CheckType::CHECK_FAIL:
ReportCheckResult("CHECK_FAIL"_s, message, mf);
break;
default:
mf.DisplayStatus(IndentText(message, mf), -1);
break;
}
break;
case Message::LogLevel::LOG_VERBOSE:
case Message::LogLevel::LOG_DEBUG:
case Message::LogLevel::LOG_TRACE:
mf.DisplayStatus(IndentText(message, mf), -1);
break;
default:
assert("Unexpected log level! Review the `cmMessageCommand.cxx`." &&
false);
break;
}
if (fatal) {
cmSystemTools::SetFatalErrorOccurred();
}
return true;
}