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