Verified Commit 949a1e12 authored by Alex Turbov's avatar Alex Turbov
Browse files

message: New message types to mark checks performed by CMake



Closes #19638.
Co-Authored-By: Craig Scott's avatarCraig Scott <craig.scott@crascit.com>
parent 25eef45f
message message
------- -------
Display a message to the user. Log a message.
Synopsis
^^^^^^^^
.. parsed-literal::
`General messages`_
message([<mode>] "message text" ...)
`Reporting checks`_
message(<checkState> "message text" ...)
General messages
^^^^^^^^^^^^^^^^
.. code-block:: cmake .. code-block:: cmake
message([<mode>] "message to display" ...) message([<mode>] "message text" ...)
Record the specified message text in the log. If more than one message
string is given, they are concatenated into a single message with no
separator between the strings.
The optional ``<mode>`` keyword determines the type of message: The optional ``<mode>`` keyword determines the type of message, which
influences the way the message is handled:
``FATAL_ERROR`` ``FATAL_ERROR``
CMake Error, stop processing and generation. CMake Error, stop processing and generation.
...@@ -82,3 +102,81 @@ usage examples. ...@@ -82,3 +102,81 @@ usage examples.
CMake Warning and Error message text displays using a simple markup CMake Warning and Error message text displays using a simple markup
language. Non-indented text is formatted in line-wrapped paragraphs language. Non-indented text is formatted in line-wrapped paragraphs
delimited by newlines. Indented text is considered pre-formatted. delimited by newlines. Indented text is considered pre-formatted.
Reporting checks
^^^^^^^^^^^^^^^^
A common pattern in CMake output is a message indicating the start of some
sort of check, followed by another message reporting the result of that check.
For example:
.. code-block:: cmake
message(STATUS "Looking for someheader.h")
#... do the checks, set checkSuccess with the result
if(checkSuccess)
message(STATUS "Looking for someheader.h - found")
else()
message(STATUS "Looking for someheader.h - not found")
endif()
This can be more robustly and conveniently expressed using the ``CHECK_...``
keyword form of the ``message()`` command:
.. code-block:: cmake
message(<checkState> "message" ...)
where ``<checkState>`` must be one of the following:
``CHECK_START``
Record a concise message about the check about to be performed.
``CHECK_PASS``
Record a successful result for a check.
``CHECK_FAIL``
Record an unsuccessful result for a check.
When recording a check result, the command repeats the message from the most
recently started check for which no result has yet been reported, then some
separator characters and then the message text provided after the
``CHECK_PASS`` or ``CHECK_FAIL`` keyword. Check messages are always reported
at ``STATUS`` log level.
Checks may be nested and every ``CHECK_START`` should have exactly one
matching ``CHECK_PASS`` or ``CHECK_FAIL``.
The :variable:`CMAKE_MESSAGE_INDENT` variable can also be used to add
indenting to nested checks if desired. For example:
.. code-block:: cmake
message(CHECK_START "Finding my things")
list(APPEND CMAKE_MESSAGE_INDENT " ")
unset(missingComponents)
message(CHECK_START "Finding partA")
# ... do check, assume we find A
message(CHECK_PASS "found")
message(CHECK_START "Finding partB")
# ... do check, assume we don't find B
list(APPEND missingComponents B)
message(CHECK_FAIL "not found")
list(POP_BACK CMAKE_MESSAGE_INDENT)
if(missingComponents)
message(CHECK_FAIL "missing components: ${missingComponents}")
else()
message(CHECK_PASS "all components found")
endif()
Output from the above would appear something like the following::
-- Finding my things
-- Finding partA
-- Finding partA - found
-- Finding partB
-- Finding partB - not found
-- Finding my things - missing components: B
new-message-types
-----------------
* The :command:`message` command gained new keywords ``CHECK_START``,
``CHECK_PASS`` and ``CHECK_FAIL``.
...@@ -3,6 +3,11 @@ ...@@ -3,6 +3,11 @@
#include "cmMessageCommand.h" #include "cmMessageCommand.h"
#include <cassert> #include <cassert>
#include <utility>
#include <cm/string_view>
#include "cm_static_string_view.hxx"
#include "cmExecutionStatus.h" #include "cmExecutionStatus.h"
#include "cmMakefile.h" #include "cmMakefile.h"
...@@ -13,6 +18,55 @@ ...@@ -13,6 +18,55 @@
#include "cmSystemTools.h" #include "cmSystemTools.h"
#include "cmake.h" #include "cmake.h"
namespace {
enum class CheckingType
{
UNDEFINED,
CHECK_START,
CHECK_PASS,
CHECK_FAIL
};
std::string IndentText(std::string text, cmMakefile& mf)
{
auto indent =
cmJoin(cmExpandedList(mf.GetSafeDefinition("CMAKE_MESSAGE_INDENT")), "");
const auto showContext = mf.GetCMakeInstance()->GetShowLogContext() ||
mf.IsOn("CMAKE_MESSAGE_CONTEXT_SHOW");
if (showContext) {
auto context = cmJoin(
cmExpandedList(mf.GetSafeDefinition("CMAKE_MESSAGE_CONTEXT")), ".");
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::AUTHOR_WARNING,
cmStrCat("Ignored "_s, what, " without CHECK_START"_s),
mf.GetBacktrace());
}
}
} // anonymous namespace
// cmLibraryCommand // cmLibraryCommand
bool cmMessageCommand(std::vector<std::string> const& args, bool cmMessageCommand(std::vector<std::string> const& args,
cmExecutionStatus& status) cmExecutionStatus& status)
...@@ -29,6 +83,7 @@ bool cmMessageCommand(std::vector<std::string> const& args, ...@@ -29,6 +83,7 @@ bool cmMessageCommand(std::vector<std::string> const& args,
auto type = MessageType::MESSAGE; auto type = MessageType::MESSAGE;
auto fatal = false; auto fatal = false;
auto level = cmake::LogLevel::LOG_UNDEFINED; auto level = cmake::LogLevel::LOG_UNDEFINED;
auto checkingType = CheckingType::UNDEFINED;
if (*i == "SEND_ERROR") { if (*i == "SEND_ERROR") {
type = MessageType::FATAL_ERROR; type = MessageType::FATAL_ERROR;
level = cmake::LogLevel::LOG_ERROR; level = cmake::LogLevel::LOG_ERROR;
...@@ -55,6 +110,18 @@ bool cmMessageCommand(std::vector<std::string> const& args, ...@@ -55,6 +110,18 @@ bool cmMessageCommand(std::vector<std::string> const& args,
return true; return true;
} }
++i; ++i;
} else if (*i == "CHECK_START") {
level = cmake::LogLevel::LOG_STATUS;
checkingType = CheckingType::CHECK_START;
++i;
} else if (*i == "CHECK_PASS") {
level = cmake::LogLevel::LOG_STATUS;
checkingType = CheckingType::CHECK_PASS;
++i;
} else if (*i == "CHECK_FAIL") {
level = cmake::LogLevel::LOG_STATUS;
checkingType = CheckingType::CHECK_FAIL;
++i;
} else if (*i == "STATUS") { } else if (*i == "STATUS") {
level = cmake::LogLevel::LOG_STATUS; level = cmake::LogLevel::LOG_STATUS;
++i; ++i;
...@@ -111,28 +178,6 @@ bool cmMessageCommand(std::vector<std::string> const& args, ...@@ -111,28 +178,6 @@ bool cmMessageCommand(std::vector<std::string> const& args,
auto message = cmJoin(cmMakeRange(i, args.cend()), ""); auto message = cmJoin(cmMakeRange(i, args.cend()), "");
if (cmake::LogLevel::LOG_NOTICE <= level) {
auto indent =
cmJoin(cmExpandedList(mf.GetSafeDefinition("CMAKE_MESSAGE_INDENT")), "");
if (!indent.empty()) {
cmSystemTools::ReplaceString(message, "\n", "\n" + indent);
message = indent + message;
}
const auto showContext = mf.GetCMakeInstance()->GetShowLogContext() ||
mf.IsOn("CMAKE_MESSAGE_CONTEXT_SHOW");
if (showContext) {
// Output the current context (if any)
auto context = cmJoin(
cmExpandedList(mf.GetSafeDefinition("CMAKE_MESSAGE_CONTEXT")), ".");
if (!context.empty()) {
context = "[" + context + "] ";
cmSystemTools::ReplaceString(message, "\n", "\n" + context);
message = context + message;
}
}
}
switch (level) { switch (level) {
case cmake::LogLevel::LOG_ERROR: case cmake::LogLevel::LOG_ERROR:
case cmake::LogLevel::LOG_WARNING: case cmake::LogLevel::LOG_WARNING:
...@@ -141,14 +186,34 @@ bool cmMessageCommand(std::vector<std::string> const& args, ...@@ -141,14 +186,34 @@ bool cmMessageCommand(std::vector<std::string> const& args,
break; break;
case cmake::LogLevel::LOG_NOTICE: case cmake::LogLevel::LOG_NOTICE:
cmSystemTools::Message(message); cmSystemTools::Message(IndentText(message, mf));
break; break;
case cmake::LogLevel::LOG_STATUS: case cmake::LogLevel::LOG_STATUS:
switch (checkingType) {
case CheckingType::CHECK_START:
mf.DisplayStatus(IndentText(message, mf), -1);
mf.GetCMakeInstance()->PushCheckInProgressMessage(message);
break;
case CheckingType::CHECK_PASS:
ReportCheckResult("CHECK_PASS"_s, message, mf);
break;
case CheckingType::CHECK_FAIL:
ReportCheckResult("CHECK_FAIL"_s, message, mf);
break;
default:
mf.DisplayStatus(IndentText(message, mf), -1);
break;
}
break;
case cmake::LogLevel::LOG_VERBOSE: case cmake::LogLevel::LOG_VERBOSE:
case cmake::LogLevel::LOG_DEBUG: case cmake::LogLevel::LOG_DEBUG:
case cmake::LogLevel::LOG_TRACE: case cmake::LogLevel::LOG_TRACE:
mf.DisplayStatus(message, -1); mf.DisplayStatus(IndentText(message, mf), -1);
break; break;
default: default:
......
...@@ -5,12 +5,15 @@ ...@@ -5,12 +5,15 @@
#include "cmConfigure.h" // IWYU pragma: keep #include "cmConfigure.h" // IWYU pragma: keep
#include <cstddef>
#include <functional> #include <functional>
#include <map> #include <map>
#include <memory> #include <memory>
#include <set> #include <set>
#include <stack>
#include <string> #include <string>
#include <unordered_set> #include <unordered_set>
#include <utility>
#include <vector> #include <vector>
#include "cmGeneratedFileStream.h" #include "cmGeneratedFileStream.h"
...@@ -387,6 +390,25 @@ public: ...@@ -387,6 +390,25 @@ public:
void SetLogLevel(LogLevel level) { this->MessageLogLevel = level; } void SetLogLevel(LogLevel level) { this->MessageLogLevel = level; }
static LogLevel StringToLogLevel(const std::string& levelStr); static LogLevel StringToLogLevel(const std::string& levelStr);
bool HasCheckInProgress() const
{
return !this->CheckInProgressMessages.empty();
}
std::size_t GetCheckInProgressSize() const
{
return this->CheckInProgressMessages.size();
}
std::string GetTopCheckInProgressMessage()
{
auto message = this->CheckInProgressMessages.top();
this->CheckInProgressMessages.pop();
return message;
}
void PushCheckInProgressMessage(std::string message)
{
this->CheckInProgressMessages.emplace(std::move(message));
}
//! Do we want debug output during the cmake run. //! Do we want debug output during the cmake run.
bool GetDebugOutput() { return this->DebugOutput; } bool GetDebugOutput() { return this->DebugOutput; }
void SetDebugOutputOn(bool b) { this->DebugOutput = b; } void SetDebugOutputOn(bool b) { this->DebugOutput = b; }
...@@ -596,6 +618,8 @@ private: ...@@ -596,6 +618,8 @@ private:
bool LogLevelWasSetViaCLI = false; bool LogLevelWasSetViaCLI = false;
bool LogContext = false; bool LogContext = false;
std::stack<std::string> CheckInProgressMessages;
void UpdateConversionPathTable(); void UpdateConversionPathTable();
//! Print a list of valid generators to stderr. //! Print a list of valid generators to stderr.
......
...@@ -83,3 +83,8 @@ run_cmake_command( ...@@ -83,3 +83,8 @@ run_cmake_command(
message-context-cli-wins-cache message-context-cli-wins-cache
${CMAKE_COMMAND} --log-level=verbose --log-context -DCMAKE_MESSAGE_CONTEXT_SHOW=OFF -P ${RunCMake_SOURCE_DIR}/message-context.cmake ${CMAKE_COMMAND} --log-level=verbose --log-context -DCMAKE_MESSAGE_CONTEXT_SHOW=OFF -P ${RunCMake_SOURCE_DIR}/message-context.cmake
) )
run_cmake_command(
message-checks
${CMAKE_COMMAND} -P ${RunCMake_SOURCE_DIR}/message-checks.cmake
)
^CMake Warning \(dev\) at.*/Tests/RunCMake/message/message-checks.cmake:13 \(message\):
Ignored CHECK_FAIL without CHECK_START
This warning is for project developers. Use -Wno-dev to suppress it.$
-- Find `libfoo`
-- Looking for `libfoo\.h`
-- Looking for `libfoo\.h` - found \[/usr/include\]
-- Looking for `libfoo\.so`
-- Looking for `libfoo\.so` - found \[/usr/lib/libfoo\.so\]
-- Getting `libfoo` version
-- Looking for `libfoo/version\.h`
-- Looking for `libfoo/version\.h` - found
-- Getting `libfoo` version - 1\.2\.3
-- Find `libfoo` - required version 4\.5\.6 but found 1\.2\.3
message(CHECK_START "Find `libfoo`")
message(CHECK_START "Looking for `libfoo.h`")
message(CHECK_PASS "found [/usr/include]")
message(CHECK_START "Looking for `libfoo.so`")
message(CHECK_PASS "found [/usr/lib/libfoo.so]")
message(CHECK_START "Getting `libfoo` version")
message(CHECK_START "Looking for `libfoo/version.h`")
message(CHECK_PASS "found")
message(CHECK_PASS "1.2.3")
message(CHECK_FAIL "required version 4.5.6 but found 1.2.3")
# Should generate an error, no associated CHECK_START
message(CHECK_FAIL "unmatched check fail case")
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment