diff --git a/doc/corrade-changelog.dox b/doc/corrade-changelog.dox index 8a10396a2..1c48aa054 100644 --- a/doc/corrade-changelog.dox +++ b/doc/corrade-changelog.dox @@ -70,6 +70,9 @@ namespace Corrade { - @ref Utility::Debug::packed and @ref Utility::Debug::color output modifiers for tighter printing of container types and printing color-like values as actual 24bit ANSI color sequences +- Ability to optionally prefix @ref Utility::Debug output with a source file + and line on supported compilers. See @ref Utility-Debug-source-location for + more information. - New @ref Utility::Directory::append() and @ref Utility::Directory::appendString() counterparts to @ref Utility::Directory::write() diff --git a/doc/snippets/Utility.cpp b/doc/snippets/Utility.cpp index d7885859b..751a0538c 100644 --- a/doc/snippets/Utility.cpp +++ b/doc/snippets/Utility.cpp @@ -360,6 +360,19 @@ Utility::Debug{} << "this has default color again"; /* [Debug-modifiers-colors-scoped] */ } +{ +/* [Debug-source-location] */ +float a = 336; + +!Utility::Debug{} << "the result is" << (a /= 8); +!Utility::Debug{} << "but here it's" << (a /= 8); + +!Utility::Debug{}; + +Utility::Debug{} << "and finally, " << (a *= 8); +/* [Debug-source-location] */ +} + { /* [Debug-nospace] */ Utility::Debug{} << "Value:" << 16 << Utility::Debug::nospace << "," << 24; diff --git a/src/Corrade/Corrade.h b/src/Corrade/Corrade.h index 17ead3af7..70de69bca 100644 --- a/src/Corrade/Corrade.h +++ b/src/Corrade/Corrade.h @@ -295,6 +295,16 @@ building Corrade. */ #define CORRADE_UTILITY_USE_ANSI_COLORS #undef CORRADE_UTILITY_USE_ANSI_COLORS + +/** +@brief Source location support in debug output + +Defined if @ref Corrade::Utility::Debug "Utility::Debug" is able to print +source location support. Available only on GCC 8.1 and newer. See +@ref Utility-Debug-source-location for more information. +*/ +#define CORRADE_UTILITY_DEBUG_HAS_SOURCE_LOCATION +#undef CORRADE_UTILITY_DEBUG_HAS_SOURCE_LOCATION #endif } diff --git a/src/Corrade/Utility/Debug.cpp b/src/Corrade/Utility/Debug.cpp index b0d8b8d0a..02dc002e0 100644 --- a/src/Corrade/Utility/Debug.cpp +++ b/src/Corrade/Utility/Debug.cpp @@ -56,6 +56,10 @@ #include "Corrade/Containers/EnumSet.hpp" #include "Corrade/Utility/DebugStl.h" +#ifdef CORRADE_UTILITY_DEBUG_HAS_SOURCE_LOCATION +#include "Corrade/Utility/Assert.h" +#endif + namespace Corrade { namespace Utility { namespace { @@ -299,6 +303,15 @@ Debug::Debug(std::ostream* const output, const Flags flags): _flags{InternalFlag #endif } +#if !defined(DOXYGEN_GENERATING_OUTPUT) && defined(CORRADE_UTILITY_DEBUG_HAS_SOURCE_LOCATION) +namespace Implementation { +DebugSourceLocation::DebugSourceLocation(Debug&& debug, const std::experimental::source_location& location): debug{&debug} { + debug._sourceLocationFile = location.file_name(); + debug._sourceLocationLine = location.line(); +} +} +#endif + Warning::Warning(std::ostream* const output, const Flags flags): Debug{flags} { /* Save previous global output and replace it with current one */ _previousGlobalWarningOutput = globalWarningOutput; @@ -316,6 +329,16 @@ Warning::Warning(const Flags flags): Warning{globalWarningOutput, flags} {} Error::Error(const Flags flags): Error{globalErrorOutput, flags} {} void Debug::cleanupOnDestruction() { + #ifdef CORRADE_UTILITY_DEBUG_HAS_SOURCE_LOCATION + /* Print source location if not printed yet -- this means saying a + !Debug{}; will print just that, while Debug{}; is a no-op */ + if(_output && _sourceLocationFile) { + CORRADE_INTERNAL_ASSERT(_immediateFlags & InternalFlag::NoSpace); + *_output << _sourceLocationFile << ":" << _sourceLocationLine; + _flags |= InternalFlag::ValueWritten; + } + #endif + /* Reset output color */ resetColorInternal(); @@ -355,6 +378,15 @@ Fatal::~Fatal() { template Debug& Debug::print(const T& value) { if(!_output) return *this; + #ifdef CORRADE_UTILITY_DEBUG_HAS_SOURCE_LOCATION + /* Print source location, if not printed yet */ + if(_sourceLocationFile) { + CORRADE_INTERNAL_ASSERT(_immediateFlags & InternalFlag::NoSpace); + *_output << _sourceLocationFile << ":" << _sourceLocationLine << ": "; + _sourceLocationFile = nullptr; + } + #endif + /* Separate values with spaces if enabled; reset all internal flags after */ if(!((_immediateFlags|_flags) & InternalFlag::NoSpace)) *_output << ' '; diff --git a/src/Corrade/Utility/Debug.h b/src/Corrade/Utility/Debug.h index 09fff896b..9cdd5eafe 100644 --- a/src/Corrade/Utility/Debug.h +++ b/src/Corrade/Utility/Debug.h @@ -40,8 +40,23 @@ #include "Corrade/Utility/Utility.h" #include "Corrade/Utility/visibility.h" +#ifdef CORRADE_UTILITY_DEBUG_HAS_SOURCE_LOCATION +#if CORRADE_CXX_STANDARD < 201401 +/* This feature could work even under C++11 if it didn't use constexpr */ +#define constexpr /** @todo UGH what?! (submit a PR with this?) */ +#endif +#include +#if CORRADE_CXX_STANDARD < 201401 +#undef constexpr +#endif +#endif + namespace Corrade { namespace Utility { +#ifdef CORRADE_UTILITY_DEBUG_HAS_SOURCE_LOCATION +namespace Implementation { struct DebugSourceLocation; } +#endif + /** @brief Debug output handler @@ -130,6 +145,31 @@ documentation for more information. @include UtilityDebug-color-greyscale.ansi +@section Utility-Debug-source-location Source location + +Similarly to the [dbg! macro in Rust](https://blog.rust-lang.org/2019/01/17/Rust-1.32.0.html#the-dbg-macro), +on supported compilers the utility is able to print source file location and +line where the debug output was executed, improving the "printf debugging" +experience. By default no source location info is printed, in order to do that +prefix the @ref Debug instantiation with an exclamation mark. Additionally, +an otherwise unused exclamated instantiation prints just the file + line alone +(in contrast to unexclamated instantiaton, which is a no-op): + +@snippet Utility.cpp Debug-source-location + +The above code then may print something like this: + +@code{.shell-session} +main.cpp:10: the result is 42 +main.cpp:11: the result is 5.25 +main.cpp:13 +and finally, 42 +@endcode + +At the moment, this feature is available only on GCC 8.1 and newer, elsewhere +it behaves like the unexclamated version. You can check for its availability +using the @ref CORRADE_UTILITY_DEBUG_HAS_SOURCE_LOCATION predefined macro. + @section Utility-Debug-multithreading Thread safety If Corrade is compiled with @ref CORRADE_BUILD_MULTITHREADED enabled (the @@ -603,6 +643,10 @@ class CORRADE_UTILITY_EXPORT Debug { InternalFlags _immediateFlags; private: + #ifdef CORRADE_UTILITY_DEBUG_HAS_SOURCE_LOCATION + friend Implementation::DebugSourceLocation; + #endif + template CORRADE_UTILITY_LOCAL static Modifier colorInternal(); CORRADE_UTILITY_LOCAL void resetColorInternal(); @@ -614,6 +658,10 @@ class CORRADE_UTILITY_EXPORT Debug { Color _previousColor; bool _previousColorBold; #endif + #ifdef CORRADE_UTILITY_DEBUG_HAS_SOURCE_LOCATION + const char* _sourceLocationFile{}; + int _sourceLocationLine{}; + #endif }; /** @debugoperatorclassenum{Debug,Debug::Color} */ @@ -628,6 +676,30 @@ CORRADE_UTILITY_EXPORT Debug& operator<<(Debug& debug, Debug::Flags value); CORRADE_ENUMSET_OPERATORS(Debug::Flags) CORRADE_ENUMSET_OPERATORS(Debug::InternalFlags) +#ifdef CORRADE_UTILITY_DEBUG_HAS_SOURCE_LOCATION +namespace Implementation { + struct CORRADE_UTILITY_EXPORT DebugSourceLocation { + /*implicit*/ DebugSourceLocation(Debug&& a, const std::experimental::source_location& location = std::experimental::source_location::current()); + Debug* debug; + }; +} + +/* Unfortunately it's not possible to add additional (default) arguments to + operator! so we need to use a implicitly convertible type and capture the + source location in its constructor */ +inline Debug& operator!(Implementation::DebugSourceLocation debug) { + return *debug.debug; +} +#else +/** @relatesalso Debug +@brief Prefix the output with source location + +Only on supported compilers, does nothing otherwise. See +@ref Utility-Debug-source-location for more information. +*/ +inline Debug& operator!(Debug&& debug) { return debug; } +#endif + #ifndef DOXYGEN_GENERATING_OUTPUT /* so Debug() << value works */ template inline Debug& operator<<(Debug&& debug, const T& value) { diff --git a/src/Corrade/Utility/Test/DebugTest.cpp b/src/Corrade/Utility/Test/DebugTest.cpp index 742179bcd..c11d88c6b 100644 --- a/src/Corrade/Utility/Test/DebugTest.cpp +++ b/src/Corrade/Utility/Test/DebugTest.cpp @@ -89,6 +89,8 @@ struct DebugTest: TestSuite::Tester { #ifndef CORRADE_TARGET_EMSCRIPTEN void multithreaded(); #endif + + void sourceLocation(); }; DebugTest::DebugTest() { @@ -154,7 +156,8 @@ DebugTest::DebugTest() { #ifndef CORRADE_TARGET_EMSCRIPTEN &DebugTest::multithreaded, #endif - }); + + &DebugTest::sourceLocation}); } void DebugTest::debug() { @@ -917,6 +920,36 @@ void DebugTest::multithreaded() { } #endif +void DebugTest::sourceLocation() { + std::ostringstream out; + + { + Debug redirect{&out}; + + !Debug{} << "hello"; + + !Debug{} << "and this is from another line"; + + !Debug{}; + + Debug{} << "this no longer"; + } + + #ifdef CORRADE_UTILITY_DEBUG_HAS_SOURCE_LOCATION + CORRADE_COMPARE(out.str(), + __FILE__ ":929: hello\n" + __FILE__ ":931: and this is from another line\n" + __FILE__ ":933\n" + "this no longer\n"); + #else + CORRADE_COMPARE(out.str(), + "hello\n" + "and this is from another line\n" + "this no longer\n"); + CORRADE_SKIP("std::experimental::source_location not available."); + #endif +} + }}}} CORRADE_TEST_MAIN(Corrade::Utility::Test::DebugTest) diff --git a/src/Corrade/configure.h.cmake b/src/Corrade/configure.h.cmake index d50836f30..df8c594f6 100644 --- a/src/Corrade/configure.h.cmake +++ b/src/Corrade/configure.h.cmake @@ -110,4 +110,9 @@ /* Otherwise no idea. */ #endif +/* Source location support in Debug */ +#if defined(__GNUC__) && __GNUC__*100 + __GNUC_MINOR__ >= 801 +#define CORRADE_UTILITY_DEBUG_HAS_SOURCE_LOCATION +#endif + #endif