#!/usr/bin/python3
#
# Copyright (C) 2016 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import sys

def usage():
    print("""USAGE:
    Simplifies a build.log from hundreds of megabytes to <100 lines. Prints output to terminal.
    Pass this script a filepath to parse. You should be able to type "python3 build_log_simplifier.py"
    And then drag-and-drop a log file onto the terminal window to get its path.

    Sample usage: python3 development/build_log_simplifier.py Users/owengray/Desktop/build.log
    """)
    exit(1)

def select_failing_task_output(lines):
    tasks_of_interest = []
    # first, find tasks of interest
    for line in lines:
        if line.startswith("Execution failed for task"):
            tasks_of_interest.append(line.split("task '")[1][:-3])


    print("Detected these failing tasks: " + str(tasks_of_interest))

    # next, save all excerpts between start(interesting task) and end(interesting task)
    current_interesting_tasks = []
    retained_lines = []
    for line in lines:
        if line.startswith("Task ") and line.split(" ")[1] in tasks_of_interest:
            if line.split(" ")[-1].strip() == "Starting":
                current_interesting_tasks.append(line.split(" ")[1])
            elif line.split(" ")[-1].strip() == "Finished":
                current_interesting_tasks.remove(line.split(" ")[1])
                retained_lines.append(line)
        if current_interesting_tasks: retained_lines.append(line)
    if retained_lines:
        return retained_lines
    # if no output was created by any failing tasks, then maybe there could be useful output from
    # somewhere else
    return lines

def shorten_uninteresting_stack_frames(lines):
    result = []
    prev_line_is_boring = False
    for line in lines:
        if line.startswith("\tat org.gradle"):
            if not prev_line_is_boring:
                result.append("\tat org.gradle...\n")
            prev_line_is_boring = True
        elif line.startswith("\tat java.base"):
            if not prev_line_is_boring:
                result.append("\tat java.base...")
            prev_line_is_boring = True
        else:
            result.append(line)
            prev_line_is_boring = False
    return result

def remove_known_uninteresting_lines(lines):
  skipLines = {
      "A fine-grained performance profile is available: use the --scan option.",
      "* Get more help at https://help.gradle.org",
      "Use '--warning-mode all' to show the individual deprecation warnings.",
      "See https://docs.gradle.org/6.5/userguide/command_line_interface.html#sec:command_line_warnings"

      "Note: Some input files use or override a deprecated API.",
      "Note: Recompile with -Xlint:deprecation for details."
  }
  skipPrefixes = [
      "See the profiling report at:",

      "Deprecated Gradle features were used in this build"
  ]
  result = []
  for line in lines:
      stripped = line.strip()
      if stripped in skipLines:
          continue
      include = True
      for prefix in skipPrefixes:
          if stripped.startswith(prefix):
              include = False
              break
      if include:
          result.append(line)
  return result

def collapse_consecutive_blank_lines(lines):
    result = []
    prev_blank = False
    for line in lines:
        if line.strip() == "":
            if not prev_blank:
                result.append(line)
            prev_blank = True
        else:
            result.append(line)
            prev_blank = False
    return result

# If multiple tasks have no output, this function removes all but the first and last
# For example, turns this:
#  > Task :a
#  > Task :b
#  > Task :c
#  > Task :d
# into this:
#  > Task :a
#  > Task ...
#  > Task :d
def collapse_tasks_having_no_output(lines):
    result = []
    pending_tasks = []
    for line in lines:
        is_task = line.startswith("> Task ")
        if is_task:
            pending_tasks.append(line)
        elif line.strip() == "":
            # If only blank lines occur between tasks, skip those blank lines
            if len(pending_tasks) > 0:
              pending_tasks.append(line)
            else:
              result.append(line)
        else:
            if len(pending_tasks) > 0:
                result += pending_tasks[0]
                if len(pending_tasks) > 2:
                    result += "> Task ...\n"
                if len(pending_tasks) > 1:
                    result += pending_tasks[-1]
                pending_tasks = []
            result.append(line)
    return result

try:
    build_log_loc = sys.argv[1]

    infile = open(build_log_loc)
    lines = infile.readlines()
    infile.close()

    lines = select_failing_task_output(lines)
    lines = shorten_uninteresting_stack_frames(lines)
    lines = remove_known_uninteresting_lines(lines)
    lines = collapse_consecutive_blank_lines(lines)
    lines = collapse_tasks_having_no_output(lines)

    print(len(lines))
    print(''.join(lines))
except Exception as e:
    print("An error occurred! "+str(e))
    usage()

@@ -3141,16 +3141,17 @@
 arnings%22
+,
 %0A%0A      
@@ -3270,16 +3270,144 @@
 etails.%22
+,%0A      %22Note: Some input files use unchecked or unsafe operations.%22,%0A      %22Note: Recompile with -Xlint:unchecked for details.%22
 %0A  %7D%0A  s

Improve build_log_simplifier.py