On 18/07/25 13:22, Filip Kastl wrote:
External email: Use caution opening links or attachments


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? ...

Hi,

Thanks for the review! No, there is no reason for it being CamelCase. I had 
written
the code in CamelCase originally then 'sed s/CamelCase/snake_case/g'd the 
functions.
I forgot to update this one :)

Looks like I also forgot to update the Colors class as well.


+
+
+################################################################################
+# 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.

Ah, my bad, this is a consequence of how it was written originally - I didn't 
have
the "cxx = cxx.replace("+", r"\+")" code, so it was "cxx = r"g\+\+".

Thanks again for the review! Here's an updated version of the patch:

-- >8 --

From 2074318c6ff513d7ef791f485f4bab17cd7edd95 Mon Sep 17 00:00:00 2001
From: Dhruv Chawla <dhr...@nvidia.com>
Date: Wed, 16 Jul 2025 07:44:25 -0700
Subject: [PATCH] [contrib] Add process_make.py

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..85ae9921fb3
--- /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 tty_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 = tty_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 = "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 print_usage():
+    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.")
+
+
+################################################################################
+# 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":
+            print_usage()
+            sys.exit(0)
+        print_usage()
+        sys.exit(1)
+
+    cc = "gcc"
+    cxx = "g++"
+    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


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


--
Regards,
Dhruv
From 2074318c6ff513d7ef791f485f4bab17cd7edd95 Mon Sep 17 00:00:00 2001
From: Dhruv Chawla <dhr...@nvidia.com>
Date: Wed, 16 Jul 2025 07:44:25 -0700
Subject: [PATCH] [contrib] Add process_make.py

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..85ae9921fb3
--- /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 tty_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 = tty_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 = "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 print_usage():
+    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.")
+
+
+################################################################################
+# 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":
+            print_usage()
+            sys.exit(0)
+        print_usage()
+        sys.exit(1)
+
+    cc = "gcc"
+    cxx = "g++"
+    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