blob: f6e6df9a686bb32822a266ed53e309b073aa4f98 [file] [log] [blame]
Igor Murashkine5193c62017-06-30 13:30:46 -07001#!/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())