54

I want to have message function in CMakeLists.txt which output colorized text. Maybe an escape sequence.

For example:

message("\x1b[31m;This text must be in red")

It don't work. I got:

Syntax error in cmake code at

/home/taurus/cmakecolor/CMakeLists.txt:1

when parsing string

\x1b[31m;This text must be in red

Invalid escape sequence \x
starball
  • 20,030
  • 7
  • 43
  • 238
Ivan Romanov
  • 1,138
  • 1
  • 9
  • 24
  • The error is because you didn't escape the backslash. Should be `message("\\x1b[31m;Red")`, but that still doesn't work… – Simon Sep 23 '13 at 21:16
  • No luck with `cmake -E echo` either. I don't think it's gonna work with just CMake, you'll probably have to call an external command, like `echo`. – Simon Sep 23 '13 at 21:20
  • Adding a literal escape works fine for me if you're editor allows it. For example, in vim insert mode type `ctrl-v` then type `escape` and you'll get a single character `^[`. Looking for an escape sequence that cmake will actually honor yet. – grim Oct 15 '13 at 00:43

4 Answers4

94

To extend @grim's correct answer, you can make things a bit more convenient by setting up variables to handle the various colours:

if(NOT WIN32)
  string(ASCII 27 Esc)
  set(ColourReset "${Esc}[m")
  set(ColourBold  "${Esc}[1m")
  set(Red         "${Esc}[31m")
  set(Green       "${Esc}[32m")
  set(Yellow      "${Esc}[33m")
  set(Blue        "${Esc}[34m")
  set(Magenta     "${Esc}[35m")
  set(Cyan        "${Esc}[36m")
  set(White       "${Esc}[37m")
  set(BoldRed     "${Esc}[1;31m")
  set(BoldGreen   "${Esc}[1;32m")
  set(BoldYellow  "${Esc}[1;33m")
  set(BoldBlue    "${Esc}[1;34m")
  set(BoldMagenta "${Esc}[1;35m")
  set(BoldCyan    "${Esc}[1;36m")
  set(BoldWhite   "${Esc}[1;37m")
endif()

message("This is normal")
message("${Red}This is Red${ColourReset}")
message("${Green}This is Green${ColourReset}")
message("${Yellow}This is Yellow${ColourReset}")
message("${Blue}This is Blue${ColourReset}")
message("${Magenta}This is Magenta${ColourReset}")
message("${Cyan}This is Cyan${ColourReset}")
message("${White}This is White${ColourReset}")
message("${BoldRed}This is BoldRed${ColourReset}")
message("${BoldGreen}This is BoldGreen${ColourReset}")
message("${BoldYellow}This is BoldYellow${ColourReset}")
message("${BoldBlue}This is BoldBlue${ColourReset}")
message("${BoldMagenta}This is BoldMagenta${ColourReset}")
message("${BoldCyan}This is BoldCyan${ColourReset}")
message("${BoldWhite}This is BoldWhite\n\n${ColourReset}")

If you really fancy pushing the boat out, you can replace the built-in message function with your own which colourises the output depending on the message type:

function(message)
  list(GET ARGV 0 MessageType)
  if(MessageType STREQUAL FATAL_ERROR OR MessageType STREQUAL SEND_ERROR)
    list(REMOVE_AT ARGV 0)
    _message(${MessageType} "${BoldRed}${ARGV}${ColourReset}")
  elseif(MessageType STREQUAL WARNING)
    list(REMOVE_AT ARGV 0)
    _message(${MessageType} "${BoldYellow}${ARGV}${ColourReset}")
  elseif(MessageType STREQUAL AUTHOR_WARNING)
    list(REMOVE_AT ARGV 0)
    _message(${MessageType} "${BoldCyan}${ARGV}${ColourReset}")
  elseif(MessageType STREQUAL STATUS)
    list(REMOVE_AT ARGV 0)
    _message(${MessageType} "${Green}${ARGV}${ColourReset}")
  else()
    _message("${ARGV}")
  endif()
endfunction()

message("No colour at all.")
message(STATUS "\"Colour\" is spelled correctly.")
message(AUTHOR_WARNING "\"Color\" is misspelled.")
message(WARNING "Final warning: spell \"color\" correctly.")
message(SEND_ERROR "Game over.  It's \"colour\", not \"color\".")
message(FATAL_ERROR "And there's no \"z\" in \"colourise\" either.")

I can't say that I recommend overriding the built-in message function in this way, but having said that, I've not found any major problems with doing this either.

Fraser
  • 74,704
  • 20
  • 238
  • 215
  • 1
    This sounds like a good idea for a cmake module ;) Also, nice touch on the win32 check. – grim Oct 25 '13 at 08:10
  • Is there a mistake in the scape sequence? The example works for me because changing colors. My working scape sequence is set(ColourReset "${Esc}[0m") with a 0, at least in Windows (running under python subprocess and colorama, it might use a different scape?) – drodri Aug 27 '14 at 21:33
  • @drodri I only tested this on Linux I think - you're probably right. – Fraser Aug 28 '14 at 01:33
  • 1
    Nice! seems to work with the message function, however, I am not sure what function cmake uses to print output, because their output is unaffected. – Francisco Aguilera Apr 11 '15 at 00:26
  • Produces `Segmentation fault (core dumped)` for me when included from multiple locations. – danijar Jul 06 '15 at 14:03
  • It may be better to check $ENV{SHELL} instead of WIN32. e.g. `if($ENV{SHELL} MATCHES "bash|csh|zsh"`. Sometimes `WIN32` might have a shell that supports coloring. I'll suggest an edit :) – MattSturgeon Jul 03 '16 at 20:25
  • Alternatively, just checking `if($ENV{SHELL}` would likely be enough... – MattSturgeon Jul 03 '16 at 20:47
  • 1
    ANSI Escape sequences are natively supported starting from Windows 10 th2. However, there is still some issue with cmake in cmd that prevent it from producing colored output (even though batch files work fine). – Dan M. Jun 27 '18 at 13:18
  • There's a [merge request](https://gitlab.kitware.com/cmake/cmake/-/merge_requests/6105) being actively reviewed at the moment, perhaps the next CMake version will natively colour based on message type – MattSturgeon May 18 '21 at 19:36
30

A simpler solution is probably just to use CMake's built-in capability for emitting coloured output, i.e. these commands

Different colors

cmake -E cmake_echo_color --normal hello
cmake -E cmake_echo_color --black hello
cmake -E cmake_echo_color --red hello
cmake -E cmake_echo_color --green hello
cmake -E cmake_echo_color --yellow hello
cmake -E cmake_echo_color --blue hello
cmake -E cmake_echo_color --magenta hello
cmake -E cmake_echo_color --cyan hello
cmake -E cmake_echo_color --white hello

Bold text

cmake -E cmake_echo_color --red --bold hello

No new line

cmake -E cmake_echo_color --red --no-newline hello

From CMake you can use the execute_process() command to invoke ${CMAKE_COMMAND}. You could write a convenient function for doing this.

UPDATE: Using cmake_echo_color with execute_process()

As pointed out by @sjm324 running

execute_process(COMMAND ${CMAKE_COMMAND} -E cmake_echo_color --red --bold hello)

does not work. Looking at the implementation https://github.com/Kitware/CMake/blob/10371cd6dcfc1bf601fa3e715734dbe66199e2e4/Source/kwsys/Terminal.c#L160

my guess is that standard output is not attached to the terminal and thus when CMake internally calls isatty() this fails.

I have two hacks that workaround this but really if you care about this you should contact the CMake devs for a less fragile solution

HACK 1: Set CLICOLOR_FORCE=1 environment variable.

execute_process(COMMAND 
  ${CMAKE_COMMAND} -E env CLICOLOR_FORCE=1
    ${CMAKE_COMMAND} -E cmake_echo_color --red --bold hello
)

This isn't a great idea. If you log the output of your build to a file it will have escape sequences in it because you're forcing them to be emitted always.

HACK 2: Set OUTPUT_FILE to be the TTY

Don't do this. HACK 1 is likely more portable.

execute_process(COMMAND
  /usr/bin/tty
  OUTPUT_VARIABLE TTY_NAME
OUTPUT_STRIP_TRAILING_WHITESPACE)

execute_process(COMMAND
  ${CMAKE_COMMAND} -E cmake_echo_color --red --bold hello
  OUTPUT_FILE ${TTY_NAME})
delcypher
  • 751
  • 7
  • 7
  • 1
    This does not work, e.g. `execute_process(COMMAND ${CMAKE_COMMAND} -E cmake_echo_color --red --bold hello)` will get stripped of its color. If it works for you please update your answer with exactly what you did? – svenevs Jun 10 '17 at 08:34
  • @sjm324 Yes. `execute_process(...)` doesn't work due to standard output not being attached to a terminal. I have some hacks make this work though. – delcypher Jun 12 '17 at 10:19
  • Ha! LOL nice that's definitely hacky :) STDOUT not being attached is the key. Thanks for spelling it out! – svenevs Jun 12 '17 at 10:40
  • An even better hack is to set `OUTPUT_FILE /dev/stdout ERROR_FILE /dev/stderr`. Then it inherits the file descriptor from the parent process, and should correctly determine whether it's a tty or not. Not sure about portability of this one, though I wouldn't expect it to be worse than HACK 2. – Thomas Mar 12 '20 at 15:48
  • `CLICOLOR_FORCE=1` is not a hack. It's the official way to force colors to be enabled when `isatty()` would otherwise indicate it's not. https://gitlab.kitware.com/cmake/cmake/-/merge_requests/3733 – Cameron Tacklind Dec 08 '22 at 22:09
9

While tedious, you can define a variable that contains an escape character and use it in your message strings

string(ASCII 27 ESCAPE)
message("${ESCAPE}[34mblue${ESCAPE}[0m")
grim
  • 760
  • 4
  • 13
0

Run execute_process and assign output to VARIBALE, then use the message to print.

No hack


macro ( print_color NAME )
    print ( COLOR ${NAME} "     ${NAME}" )
endmacro ()

function  ( text )
    cmake_parse_arguments ( PARSE_ARGV 0 "_TEXT" "BOLD" "COLOR" "" )

    set ( _TEXT_OPTIONS -E cmake_echo_color --no-newline )

    if ( _TEXT_COLOR )
        string ( TOLOWER "${_TEXT_COLOR}" _TEXT_COLOR_LOWER )
        if ( NOT ${_TEXT_COLOR_LOWER} MATCHES "^normal|black|red|green|yellow|blue|magenta|cyan|white" )
            print ( "Only these colours are supported:" )
            print_color ( NORMAL )
            print_color ( BLACK )
            print_color ( RED )
            print_color ( GREEN )
            print_color ( YELLOW )
            print_color ( BLUE )
            print_color ( MAGENTA )
            print_color ( CYAN )
            print_color ( WHITE )
            TEXT ( WARING "Color ${_TEXT_COLOR} is not support." )
        else ()
            list ( APPEND _TEXT_OPTIONS --${_TEXT_COLOR_LOWER} )
        endif ()
    endif ()

    if ( _TEXT_BOLD )
        list ( APPEND _TEXT_OPTIONS --bold )
    endif ()

    execute_process ( COMMAND ${CMAKE_COMMAND} -E env CLICOLOR_FORCE=1 ${CMAKE_COMMAND} ${_TEXT_OPTIONS} ${_TEXT_UNPARSED_ARGUMENTS}
                      OUTPUT_VARIABLE _TEXT_RESULT
                      ECHO_ERROR_VARIABLE
                      )

    set ( TEXT_RESULT ${_TEXT_RESULT} PARENT_SCOPE )
endfunction ()
unset ( print_color )

function ( print )
    text ( ${ARGN} )
    message ( ${TEXT_RESULT} )
endfunction ()

print ( COLOR NORMAL TEST_NORMAL )
print ( BOLD COLOR NORMAL TEST_NORMAL_BOLD )
print ( COLOR BLACK TEST_BLACK )
print ( BOLD COLOR BLACK TEST_BLACK_BOLD )
print ( COLOR RED TEST_RED )
print ( BOLD COLOR RED TEST_RED_BOLD )
print ( COLOR GREEN TEST_GREEN )
print ( BOLD COLOR GREEN TEST_GREEN_BOLD )
print ( COLOR YELLOW TEST_YELLOW )
print ( BOLD COLOR YELLOW TEST_YELLOW_BOLD )
print ( COLOR BLUE TEST_BLUE )
print ( BOLD COLOR BLUE TEST_BLUE_BOLD )
print ( COLOR MAGENTA TEST_MAGENTA )
print ( BOLD COLOR MAGENTA TEST_MAGENTA_BOLD )
print ( COLOR CYAN TEST_CYAN )
print ( BOLD COLOR CYAN TEST_CYAN_BOLD )
print ( COLOR WHITE TEST_WHITE )
print ( BOLD COLOR WHITE TEST_WHITE_BOLD )

<script src="https://gist.github.com/Invincibl-e/3a2dd433c338284cfe95f0d5a6153c06.js"></script>