CC_IS_CLANG and CC_IS_GCC added by me. Adapated from 3.2 patch. - bdrewery Original author: From cacb14929748ae93eacefcfa194aa93689d217eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lubo=C5=A1=20Lu=C5=88=C3=A1k?= Date: Fri, 29 Nov 2013 12:14:03 +0100 Subject: [PATCH] support compiler color diagnostics if possible Clang and GCC (starting with 4.9) support color output if possible, but since ccache redirects stderr to a file, they detect the output is not a terminal and do not enable colors. Try to detect if colors would be used and force colors explicitly. Caveats: - Compiles with and without colors are considered different from each other (so they are "duplicated"). - GCC decided to roll its own name for the option, so it's necessary to guess which compiler is actually used. --- ccache.c.orig 2014-03-02 00:16:33.335546870 -0600 +++ ccache.c 2014-03-02 00:16:35.888546332 -0600 @@ -849,14 +849,42 @@ hash_compiler(struct mdfour *hash, struc if (!hash_multicommand_output(hash, compilercheck, orig_args->argv[0])) { fatal("Failure running compiler check command: %s", compilercheck); } } } /* + * Note that these compiler checks are unreliable, so nothing should hard-depend on them. + */ + +static bool compiler_is_clang() +{ + const char* name = strrchr( orig_args->argv[ 0 ], '/' ); + name = name ? name + 1 : orig_args->argv[ 0 ]; +#ifdef CC_IS_CLANG + if (strcmp(name, "cc") == 0 || strcmp(name, "CC") == 0 || + strcmp(name, "c++") == 0) + return true; +#endif + return strstr( name, "clang" ) != NULL; +} + +static bool compiler_is_gcc() +{ + const char* name = strrchr(orig_args->argv[ 0 ], '/' ); +#ifdef CC_IS_GCC + if (strcmp(name, "cc") == 0 || strcmp(name, "CC") == 0 || + strcmp(name, "c++") == 0) + return true; +#endif + name = name ? name + 1 : orig_args->argv[ 0 ]; + return strstr(name, "gcc") != NULL || strstr(name, "g++") != NULL; +} + +/* * Update a hash sum with information common for the direct and preprocessor * modes. */ static void calculate_common_hash(struct args *args, struct mdfour *hash) { struct stat st; @@ -913,14 +941,23 @@ calculate_common_hash(struct args *args, stats_update(STATS_BADEXTRAFILE); failed(); } q = NULL; } free(p); } + + /* Possibly hash GCC_COLORS (for color diagnostics). */ + if (compiler_is_gcc()) { + const char* gcc_colors = getenv("GCC_COLORS"); + if (gcc_colors != NULL) { + hash_delimiter(hash,"gcccolors"); + hash_string(hash, gcc_colors); + } + } } /* * Update a hash sum with information specific to the direct and preprocessor * modes and calculate the object hash. Returns the object hash on success, * otherwise NULL. Caller frees. */ @@ -1273,14 +1310,21 @@ find_compiler(int argc, char **argv) bool is_precompiled_header(const char *path) { return str_eq(get_extension(path), ".gch"); } +static bool color_output_possible() +{ + const char* term_env = getenv("TERM"); + + return isatty(STDERR_FILENO) && term_env && strcasecmp(term_env, "DUMB") != 0; +} + /* * Process the compiler options into options suitable for passing to the * preprocessor and the real compiler. The preprocessor options don't include * -E; this is added later. Returns true on success, otherwise false. */ bool cc_process_args(struct args *orig_args, struct args **preprocessor_args, @@ -1301,14 +1345,15 @@ cc_process_args(struct args *orig_args, bool dependency_filename_specified = false; /* is the dependency makefile target name specified with -MT or -MQ? */ bool dependency_target_specified = false; struct args *stripped_args = NULL, *dep_args = NULL; int argc = orig_args->argc; char **argv = orig_args->argv; bool result = true; + bool found_color_diagnostics = false; stripped_args = args_init(0, NULL); dep_args = args_init(0, NULL); args_add(stripped_args, argv[0]); for (i = 1; i < argc; i++) { @@ -1551,14 +1596,34 @@ cc_process_args(struct args *orig_args, /* Input charset needs to be handled specially. */ if (str_startswith(argv[i], "-finput-charset=")) { input_charset = argv[i]; continue; } + if (str_eq(argv[i], "-fcolor-diagnostics") + || str_eq(argv[i], "-fno-color-diagnostics") + || str_eq(argv[i], "-fdiagnostics-color") + || str_eq(argv[i], "-fdiagnostics-color=always") + || str_eq(argv[i], "-fno-diagnostics-color") + || str_eq(argv[i], "-fdiagnostics-color=never")) { + args_add(stripped_args, argv[i]); + found_color_diagnostics = true; + continue; + } + if (str_eq(argv[i], "-fdiagnostics-color=auto")) { + if (color_output_possible()) { + /* Output is redirected, so color output must be forced. */ + args_add(stripped_args, "-fdiagnostics-color=always"); + cc_log("Automatically forcing colors"); + } + found_color_diagnostics = true; + continue; + } + /* * Options taking an argument that that we may want to rewrite * to relative paths to get better hit rate. A secondary effect * is that paths in the standard error output produced by the * compiler will be normalized. */ if (compopt_takes_path(argv[i])) { @@ -1765,14 +1830,36 @@ cc_process_args(struct args *orig_args, cc_log("Not a regular file: %s", output_obj); stats_update(STATS_DEVICE); result = false; goto out; } /* + * Since output is redirected, compilers will not color their output by default, + * so force it explicitly if it would be otherwise done. + */ + if (!found_color_diagnostics && color_output_possible()) { + if (compiler_is_clang()) { + args_add(stripped_args, "-fcolor-diagnostics"); + cc_log("Automatically enabling colors"); + } else if (compiler_is_gcc()) { + /* + * GCC has it since 4.9, but that'd require detecting what GCC + * version is used for the actual compile. However it requires + * also GCC_COLORS to be set (and not empty), so use that + * for detecting if GCC would use colors. + */ + if (getenv("GCC_COLORS") != NULL && getenv("GCC_COLORS")[ 0 ] != '\0') { + args_add(stripped_args, "-fdiagnostics-color"); + cc_log("Automatically enabling colors"); + } + } + } + + /* * Some options shouldn't be passed to the real compiler when it compiles * preprocessed code: * * -finput-charset=XXX (otherwise conversion happens twice) * -x XXX (otherwise the wrong language is selected) */ *preprocessor_args = args_copy(stripped_args);