Hi,

I've tried running it and quickly looked through the code.  Looks good to me!
I'm not a mantainer, though.

Just two nitpicks from me inline...

On Wed 2025-07-16 21:47:00, dhr...@nvidia.com wrote:
> From: Dhruv Chawla <dhr...@nvidia.com>
> 
> This is a script that makes it easier to visualize the output from make.
> It filters out most of the output, leaving only (mostly) messages about
> files being compiled, installed and linked. It is not 100% accurate in
> the matching, but I feel it does a good enough job.
> 
> To use it, simply pipe make into it:
> 
>   make | contrib/process_make.py
> 
> It also automatically colorizes the output if stdout is a TTY.
> 
> Signed-off-by: Dhruv Chawla <dhr...@nvidia.com>
> 
> contrib/ChangeLog:
> 
>       process_make.py: New script.
> ---
>  contrib/process_make.py | 383 ++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 383 insertions(+)
>  create mode 100755 contrib/process_make.py
> 
> diff --git a/contrib/process_make.py b/contrib/process_make.py
> new file mode 100755
> index 00000000000..bf5aea517a3
> --- /dev/null
> +++ b/contrib/process_make.py
> @@ -0,0 +1,383 @@
> +#!/usr/bin/env python3
> +
> +# Script to colorize the output of make, and simplify it to make reading 
> easier.
> +
> +# Copyright The GNU Toolchain Authors.
> +#
> +# This file is part of GCC.
> +#
> +# GCC is free software; you can redistribute it and/or modify it under
> +# the terms of the GNU General Public License as published by the Free
> +# Software Foundation; either version 3, or (at your option) any later
> +# version.
> +#
> +# GCC is distributed in the hope that it will be useful, but WITHOUT ANY
> +# WARRANTY; without even the implied warranty of MERCHANTABILITY or
> +# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
> +# for more details.
> +#
> +# You should have received a copy of the GNU General Public License
> +# along with GCC; see the file COPYING3.  If not see
> +# <http://www.gnu.org/licenses/>.
> +
> +import os
> +import re
> +import sys
> +
> +from os import path
> +from sys import stdin
> +
> +################################################################################
> +# Console colors
> +################################################################################
> +
> +
> +class Colors:
> +    def __init__(self):
> +        if sys.stdout.isatty():
> +            self.BLACK = "\033[30m"
> +            self.RED = "\033[31m"
> +            self.GREEN = "\033[32m"
> +            self.YELLOW = "\033[33m"
> +            self.BLUE = "\033[34m"
> +            self.MAGENTA = "\033[35m"
> +            self.CYAN = "\033[36m"
> +            self.WHITE = "\033[37m"
> +            self.UNDERLINE = "\033[4m"
> +            self.RESET = "\033[0m"
> +        else:
> +            self.BLACK = ""
> +            self.RED = ""
> +            self.GREEN = ""
> +            self.YELLOW = ""
> +            self.BLUE = ""
> +            self.MAGENTA = ""
> +            self.CYAN = ""
> +            self.WHITE = ""
> +            self.UNDERLINE = ""
> +            self.RESET = ""
> +
> +
> +colors = Colors()
> +
> +################################################################################
> +# Regular expressions to match output against
> +################################################################################
> +
> +# Messages printed by make
> +MAKE_ENTERING_DIRECTORY = r"make\[\d+\]: Entering directory '(.*)'"
> +MAKE_LEAVING_DIRECTORY = r"make\[\d+\]: Leaving directory '(.*)'"
> +MAKE_NOTHING_TO_BE_DONE = r"make\[\d+\]: Nothing to be done for '(.*)'."
> +
> +# Messages printed by configure
> +CONFIGURE_CHECKING_FOR = r"checking for (.*)\.\.\. (.*)"
> +CONFIGURE_CHECKING_HOW = r"checking how (.*)\.\.\. (.*)"
> +CONFIGURE_CHECKING_WHETHER = r"checking whether (.*)\.\.\. (.*)"
> +CONFIGURE_OTHER_CHECKS = r"checking .*"
> +
> +# File patterns
> +SOURCE_FILE_PATTERN = r"(.*\.c)|(.*\.cc)|(.*\.cxx)(.*\.m)"
> +SOURCE_FILE_PATTERN_FALLBACK = r"(.*\.o)|(.*\.lo)|(.*\.gch)"
> +LINKED_FILE_PATTERN = r"(.*\.so)|(.*\.a)"
> +LINKED_FILE_PATTERN_FALLBACK = r"(.*\.la)|(.*\.o)"
> +ARCHIVE_FILE_PATTERN = r"(.*\.a)|(.*\.la)"
> +
> +# Libtool itself
> +LIBTOOL_COMPILE = "libtool: compile: (.*)"
> +LIBTOOL_LINK = "libtool: link: (.*)"
> +LIBTOOL_INSTALL = "libtool: install: (.*)"
> +
> +# Bash invoking libtool
> +BASH_LT_COMPILE = "(?:/bin/bash (?:.*)libtool(?:.*)--mode=compile(.*))"
> +BASH_LT_LINK = "(?:/bin/bash (?:.*)libtool(?:.*)--mode=link(.*))"
> +
> +# Noisy information
> +IF_PATTERN = "if (.*)"
> +ELSE_PATTERN = "else (.*); fi(.*)"
> +ECHO_PATTERN = "(echo .*)"
> +MOVE_IF_CHANGE_PATTERN = "(/bin/bash.*move-if-change.*)"
> +MOVE_PATTERN = "mv -f (.*)"
> +REMOVE_PATTERN = "rm -r?f (.*)"
> +APPLYING_PATTERN = "Applying(.*)"
> +FIXING_PATTERN = "Fixing(.*)"
> +FIXED_PATTERN = "Fixed:(.*)"
> +COMMENT_PATTERN = "# (.*)"
> +DEPBASE_PATTERN = "depbase(.*)"
> +
> +# Compiled compiler invocation
> +XGCC_PATTERN = r"[A-Za-z0-9_\-\./]+/xgcc (.*)"
> +XGPP_PATTERN = r"[A-Za-z0-9_\-\./]+/xg\+\+ (.*)"
> +GFORTRAN_PATTERN = r"[A-Za-z0-9_\-\./]+/gfortran (.*)"
> +
> +# Awk
> +AWK_PATTERN = "(?:awk|gawk) (.*)"
> +
> +# Archive creation
> +ARCHIVE_PATTERN = "(?:ar|ranlib) (.*)"
> +
> +################################################################################
> +# Helper function (print usage)
> +################################################################################
> +
> +
> +def PrintUsage():
> +    print("Usage: process_make.py [<C compiler> <C++ compiler>]")
> +    print("")
> +    print("  Either no arguments should be specified or the exact values")
> +    print("  of $CC or $CXX passed to gcc/configure should be passed to")
> +    print("  the script.")

... Is there a reason for PrintUsage() being CamelCase instead of snake_case
like the rest of the function names? ...

> +
> +
> +################################################################################
> +# Helper functions (for extracting file names)
> +################################################################################
> +
> +
> +def search_line_for_pattern(
> +    line: str, pattern: str, ignore_start: str = "", ignore_end: str = ""
> +) -> str:
> +    arguments: list[str] = line.split(" ")
> +    for argument in arguments:
> +        if re.match(pattern, argument):
> +            if (
> +                ignore_start != ""
> +                and argument.startswith(ignore_start)
> +                or ignore_end != ""
> +                and argument.endswith(ignore_end)
> +            ):
> +                continue
> +            return argument
> +    return ""
> +
> +
> +def extract_source_file_from_line(line: str) -> str:
> +    file = search_line_for_pattern(line, SOURCE_FILE_PATTERN)
> +    if file != "":
> +        return file
> +    return search_line_for_pattern(line, SOURCE_FILE_PATTERN_FALLBACK)
> +
> +
> +def extract_linked_file_from_line(line: str) -> str:
> +    file = search_line_for_pattern(line, LINKED_FILE_PATTERN, "", 
> "liblto_plugin.so")
> +    if file != "":
> +        return file
> +    return search_line_for_pattern(line, LINKED_FILE_PATTERN_FALLBACK)
> +
> +
> +def extract_installed_file_from_line(line: str) -> str:
> +    return line.split(" ")[-1]
> +
> +
> +def get_non_null_match(matches) -> str:
> +    not_none = [*filter(lambda x: x != None, matches.groups())]
> +    assert len(not_none) == 1
> +    return not_none[0]
> +
> +
> +################################################################################
> +# Helper functions (for colored output)
> +################################################################################
> +
> +
> +def get_colored_file_path(filepath: str):
> +    filename = path.basename(filepath)
> +    directory = path.split(filepath)[0]
> +    if directory != "":
> +        return 
> f"{colors.WHITE}{directory}/{colors.GREEN}{filename}{colors.RESET}"
> +    else:
> +        return f"{colors.GREEN}{filename}{colors.RESET}"
> +
> +
> +def get_colored_path_if_non_empty(line, filename: str):
> +    if filename == "":
> +        return f"{colors.WHITE}{line}{colors.RESET}"
> +    return get_colored_file_path(filename)
> +
> +
> +################################################################################
> +# Helper class to match various compiler invocation patterns
> +################################################################################
> +
> +
> +# Compiler invocation
> +class compiler_regex:
> +    def __init__(self, cc: str, cxx: str) -> None:
> +        core_cc_pattern = f"({cc}.*)|(/usr.*{cc}.*)"
> +        core_cc_pattern = f"{core_cc_pattern}|(?:source='.*' object='.*' 
> libtool=(?:yes|no) (?:{core_cc_pattern}))"
> +        self.cc = f"^{core_cc_pattern}$"
> +        self.ignored_cc = f"^\\s+{core_cc_pattern}$"
> +
> +        cxx = cxx.replace("+", r"\+")
> +        core_cxx_pattern = f"({cxx}.*)|(/usr.*{cxx}.*)"
> +        core_cxx_pattern = f"{core_cxx_pattern}|(?:source='.*' object='.*' 
> libtool=(?:yes|no) (?:{core_cxx_pattern}))"
> +        self.cxx = f"^{core_cxx_pattern}$"
> +        self.ignored_cxx = f"^\\s+{core_cxx_pattern}$"
> +
> +        self.xgcc = XGCC_PATTERN
> +        self.xgpp = XGPP_PATTERN
> +        self.gfortran = GFORTRAN_PATTERN
> +
> +    def match_c_pattern(self, line: str):
> +        cc_match = re.match(self.cc, line)
> +        if cc_match:
> +            return cc_match
> +        return re.match(self.xgcc, line)
> +
> +    def match_cxx_pattern(self, line: str):
> +        cxx_match = re.match(self.cxx, line)
> +        if cxx_match:
> +            return cxx_match
> +        return re.match(self.xgpp, line)
> +
> +    def match_pattern(self, line: str):
> +        c_match = self.match_c_pattern(line)
> +        if c_match:
> +            return c_match
> +        cxx_match = self.match_cxx_pattern(line)
> +        if cxx_match:
> +            return cxx_match
> +        return re.match(self.gfortran, line)
> +
> +    def match_ignored_pattern(self, line: str):
> +        c_match = re.match(self.ignored_cc, line)
> +        if c_match:
> +            return c_match
> +        return re.match(self.ignored_cxx, line)
> +
> +
> +################################################################################
> +# Main routine
> +################################################################################
> +
> +
> +def main() -> None:
> +    args = sys.argv
> +    if len(args) == 2 or len(args) > 3:
> +        if args[1] == "--help" or args[1] == "-h":
> +            PrintUsage()
> +            sys.exit(0)
> +        PrintUsage()
> +        sys.exit(1)
> +
> +    cc = "gcc"
> +    cxx = r"g++"

... The 'r' here doesn't serve any purpose, I think.  As far as I understand,
these r strings are just a different type of literal.  They are used when you
don't want to escape characters.  But I believe that in "g++", there's nothing
to be escaped.

Thanks for the patch,
Filip Kastl

> +    if len(args) == 3:
> +        cc = args[1]
> +        cxx = args[2]
> +
> +    makeflags = os.getenv("MAKEFLAGS")
> +    if makeflags and "-Oline" not in makeflags:
> +        print("{colors.RED}WARNING: Please add '-Oline' to 
> $MAKEFLAGS{colors.RESET}")
> +
> +    compiling_string = f"{colors.WHITE}[{colors.YELLOW}*{colors.WHITE}] 
> {colors.YELLOW}Compiling{colors.RESET}"
> +    linking_string = f"{colors.WHITE}[{colors.CYAN}*{colors.WHITE}] 
> {colors.CYAN}Linking{colors.RESET}"
> +    installing_string = f"{colors.WHITE}[{colors.BLUE}*{colors.WHITE}] 
> {colors.BLUE}Installing{colors.RESET}"
> +    archiving_string = f"{colors.WHITE}[{colors.MAGENTA}*{colors.WHITE}] 
> {colors.MAGENTA}Creating archive{colors.RESET}"
> +
> +    patterns_dict = {
> +        f"(?:{LIBTOOL_COMPILE})|(?:{BASH_LT_COMPILE})": (
> +            compiling_string,
> +            lambda line, group: get_colored_path_if_non_empty(
> +                line, extract_source_file_from_line(group)
> +            ),
> +        ),
> +        f"(?:{LIBTOOL_LINK})|(?:{BASH_LT_LINK})": (
> +            linking_string,
> +            lambda line, group: get_colored_path_if_non_empty(
> +                line, extract_linked_file_from_line(group)
> +            ),
> +        ),
> +        LIBTOOL_INSTALL: (
> +            installing_string,
> +            lambda line, group: get_colored_path_if_non_empty(
> +                line, extract_installed_file_from_line(group)
> +            ),
> +        ),
> +        ARCHIVE_PATTERN: (
> +            archiving_string,
> +            lambda line, _: get_colored_path_if_non_empty(
> +                line, search_line_for_pattern(line, ARCHIVE_FILE_PATTERN)
> +            ),
> +        ),
> +    }
> +
> +    ignored_patterns = [
> +        MAKE_ENTERING_DIRECTORY,
> +        MAKE_LEAVING_DIRECTORY,
> +        MAKE_NOTHING_TO_BE_DONE,
> +        CONFIGURE_CHECKING_FOR,
> +        CONFIGURE_CHECKING_HOW,
> +        CONFIGURE_CHECKING_WHETHER,
> +        CONFIGURE_OTHER_CHECKS,
> +        IF_PATTERN,
> +        ELSE_PATTERN,
> +        ECHO_PATTERN,
> +        AWK_PATTERN,
> +        MOVE_IF_CHANGE_PATTERN,
> +        MOVE_PATTERN,
> +        REMOVE_PATTERN,
> +        APPLYING_PATTERN,
> +        FIXING_PATTERN,
> +        FIXED_PATTERN,
> +        COMMENT_PATTERN,
> +        DEPBASE_PATTERN,
> +    ]
> +
> +    ignored_pattern_string = "|".join(f"(?:{x})" for x in ignored_patterns)
> +    ignored_pattern_string = f"^{ignored_pattern_string}$"
> +    compiler_re = compiler_regex(cc, cxx)
> +
> +    ignore_next_line = False
> +    multiline_match = False
> +    current_line = ""
> +    for line in stdin:
> +        line = line.rstrip("\n")
> +
> +        if multiline_match:
> +            current_line += line
> +            multiline_match = line.endswith("\\")
> +            continue
> +        else:
> +            current_line = line
> +
> +        if ignore_next_line:
> +            ignore_next_line = line.endswith("\\")
> +            continue
> +
> +        line = current_line
> +        compiler_match = compiler_re.match_pattern(line)
> +
> +        if compiler_match:
> +            if line.endswith("\\"):
> +                multiline_match = True
> +                continue
> +            fmt = get_colored_path_if_non_empty(
> +                line, 
> extract_source_file_from_line(get_non_null_match(compiler_match))
> +            )
> +            print(f"{compiling_string} {fmt}{colors.RESET}")
> +            continue
> +
> +        matched = False
> +        for pattern, (header, fmt) in patterns_dict.items():
> +            match = re.match(pattern, line)
> +            if match:
> +                if line.endswith("\\"):
> +                    multiline_match = True
> +                    break
> +                print(f"{header} {fmt(line, get_non_null_match(match))}" + 
> colors.RESET)
> +                matched = True
> +                break
> +        if multiline_match or matched:
> +            continue
> +
> +        if compiler_re.match_ignored_pattern(line) or re.match(
> +            ignored_pattern_string, line
> +        ):
> +            ignore_next_line = line.endswith("\\")
> +            continue
> +
> +        print(line)
> +
> +
> +if __name__ == "__main__":
> +    main()
> -- 
> 2.44.0
> 

Reply via email to