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 25import os 26import pathlib 27import subprocess 28import sys 29import tempfile 30 31THIS_PATH = os.path.dirname(os.path.realpath(__file__)) 32 33TOOLS_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 49DEBUG = False 50 51def debug_print(msg): 52 if DEBUG: 53 print("[DEBUG]: " + msg, file=sys.stderr) 54 55def 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 62def 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 71def 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 78def 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 87def 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 93def 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 98def 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 144def 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 158if __name__ == '__main__': 159 sys.exit(main()) 160