Home | History | Annotate | Download | only in tools
      1 #!/usr/bin/python3
      2 #
      3 # Copyright 2017, The Android Open Source Project
      4 #
      5 # Licensed under the Apache License, Version 2.0 (the "License");
      6 # you may not use this file except in compliance with the License.
      7 # You may obtain a copy of the License at
      8 #
      9 #     http://www.apache.org/licenses/LICENSE-2.0
     10 #
     11 # Unless required by applicable law or agreed to in writing, software
     12 # distributed under the License is distributed on an "AS IS" BASIS,
     13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14 # See the License for the specific language governing permissions and
     15 # limitations under the License.
     16 
     17 #
     18 # There are many run-tests which generate their sources automatically.
     19 # It is desirable to keep the checked-in source code, as we re-run generators very rarely.
     20 #
     21 # This script will re-run the generators only if their dependent files have changed and then
     22 # complain if the outputs no longer matched what's in the source tree.
     23 #
     24 
     25 import os
     26 import pathlib
     27 import subprocess
     28 import sys
     29 import tempfile
     30 
     31 THIS_PATH = os.path.dirname(os.path.realpath(__file__))
     32 
     33 TOOLS_GEN_SRCS = [
     34     # tool -> path to a script to generate a file
     35     # reference_files -> list of files that the script can generate
     36     # args -> lambda(path) that generates arguments the 'tool' in order to output to 'path'
     37     # interesting_files -> which files much change in order to re-run the tool.
     38     # interesting_to_reference_files: lambda(x,reference_files)
     39     #                                 given the interesting file 'x' and a list of reference_files,
     40     #                                 return exactly one reference file that corresponds to it.
     41     { 'tool' : 'test/988-method-trace/gen_srcs.py',
     42       'reference_files' : ['test/988-method-trace/src/art/Test988Intrinsics.java'],
     43       'args' : lambda output_path: [output_path],
     44       'interesting_files' : ['compiler/intrinsics_list.h'],
     45       'interesting_to_reference_file' : lambda interesting, references: references[0],
     46     },
     47 ]
     48 
     49 DEBUG = False
     50 
     51 def debug_print(msg):
     52   if DEBUG:
     53     print("[DEBUG]: " + msg, file=sys.stderr)
     54 
     55 def is_interesting(f, tool_dict):
     56   """
     57   Returns true if this is a file we want to run this tool before uploading. False otherwise.
     58   """
     59   path = pathlib.Path(f)
     60   return str(path) in tool_dict['interesting_files']
     61 
     62 def get_changed_files(commit):
     63   """
     64   Gets the files changed in the given commit.
     65   """
     66   return subprocess.check_output(
     67       ["git", 'diff-tree', '--no-commit-id', '--name-only', '-r', commit],
     68       stderr=subprocess.STDOUT,
     69       universal_newlines=True).split()
     70 
     71 def command_line_for_tool(tool_dict, output):
     72   """
     73   Calculate the command line for this tool when ran against the output file 'output'.
     74   """
     75   proc_args = [tool_dict['tool']] + tool_dict['args'](output)
     76   return proc_args
     77 
     78 def run_tool(tool_dict, output):
     79   """
     80   Execute this tool by passing the tool args to the tool.
     81   """
     82   proc_args = command_line_for_tool(tool_dict, output)
     83   debug_print("PROC_ARGS: %s" %(proc_args))
     84   succ = subprocess.call(proc_args)
     85   return succ
     86 
     87 def get_reference_file(changed_file, tool_dict):
     88    """
     89    Lookup the file that the tool is generating in response to changing an interesting file
     90    """
     91    return tool_dict['interesting_to_reference_file'](changed_file, tool_dict['reference_files'])
     92 
     93 def run_diff(changed_file, tool_dict, original_file):
     94   ref_file = get_reference_file(changed_file, tool_dict)
     95 
     96   return subprocess.call(["diff", ref_file, original_file]) != 0
     97 
     98 def run_gen_srcs(files):
     99   """
    100   Runs test tools only for interesting files that were changed in this commit.
    101   """
    102   if len(files) == 0:
    103     return
    104 
    105   success = 0  # exit code 0 = success, >0 error.
    106   had_diffs = False
    107 
    108   for tool_dict in TOOLS_GEN_SRCS:
    109     tool_ran_at_least_once = False
    110     for f in files:
    111       if is_interesting(f, tool_dict):
    112         tmp_file = tempfile.mktemp()
    113         reference_file = get_reference_file(f, tool_dict)
    114 
    115         # Generate the source code with a temporary file as the output.
    116         success = run_tool(tool_dict, tmp_file)
    117         if success != 0:
    118           # Immediately abort if the tool fails with a non-0 exit code, do not go any further.
    119           print("[FATAL] Error when running tool (return code %s)" %(success), file=sys.stderr)
    120           print("$> %s" %(" ".join(command_line_for_tool(tool_dict, tmp_file))), file=sys.stderr)
    121           sys.exit(success)
    122         if run_diff(f, tool_dict, tmp_file):
    123           # If the tool succeeded, but there was a diff, then the generated code has diverged.
    124           # Output the diff information and continue to the next files/tools.
    125           had_diffs = True
    126           print("-----------------------------------------------------------", file=sys.stderr)
    127           print("File '%s' diverged from generated file; please re-run tools:" %(reference_file), file=sys.stderr)
    128           print("$> %s" %(" ".join(command_line_for_tool(tool_dict, reference_file))), file=sys.stderr)
    129         else:
    130           debug_print("File %s is consistent with tool %s" %(reference_file, tool_dict['tool']))
    131 
    132         tool_ran_at_least_once = True
    133 
    134     if not tool_ran_at_least_once:
    135       debug_print("Interesting files %s unchanged, skipping tool '%s'" %(tool_dict['interesting_files'], tool_dict['tool']))
    136 
    137   if had_diffs:
    138     success = 1
    139   # Always return non-0 exit code when there were diffs so that the presubmit hooks are FAILED.
    140 
    141   return success
    142 
    143 
    144 def main():
    145   if 'PREUPLOAD_COMMIT' in os.environ:
    146     commit = os.environ['PREUPLOAD_COMMIT']
    147   else:
    148     print("WARNING: Not running as a pre-upload hook. Assuming commit to check = 'HEAD'", file=sys.stderr)
    149     commit = "HEAD"
    150 
    151   os.chdir(os.path.join(THIS_PATH, '..')) # run tool relative to 'art' directory
    152   debug_print("CWD: %s" %(os.getcwd()))
    153 
    154   changed_files = get_changed_files(commit)
    155   debug_print("Changed files: %s" %(changed_files))
    156   return run_gen_srcs(changed_files)
    157 
    158 if __name__ == '__main__':
    159   sys.exit(main())
    160