Igor Murashkin | e5193c6 | 2017-06-30 13:30:46 -0700 | [diff] [blame] | 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()) |