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 >