OLD | NEW |
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
5 | 5 |
6 """Enables directory-specific presubmit checks to run at upload and/or commit. | 6 """Enables directory-specific presubmit checks to run at upload and/or commit. |
7 """ | 7 """ |
8 | 8 |
9 __version__ = '1.8.0' | 9 __version__ = '1.8.0' |
10 | 10 |
11 # TODO(joi) Add caching where appropriate/needed. The API is designed to allow | 11 # TODO(joi) Add caching where appropriate/needed. The API is designed to allow |
12 # caching (between all different invocations of presubmit scripts for a given | 12 # caching (between all different invocations of presubmit scripts for a given |
13 # change). We should add it as our presubmit scripts start feeling slow. | 13 # change). We should add it as our presubmit scripts start feeling slow. |
14 | 14 |
15 import cpplint | 15 import cpplint |
16 import cPickle # Exposed through the API. | 16 import cPickle # Exposed through the API. |
17 import cStringIO # Exposed through the API. | 17 import cStringIO # Exposed through the API. |
18 import contextlib | 18 import contextlib |
19 import fnmatch | 19 import fnmatch |
20 import glob | 20 import glob |
21 import inspect | 21 import inspect |
| 22 import itertools |
22 import json # Exposed through the API. | 23 import json # Exposed through the API. |
23 import logging | 24 import logging |
24 import marshal # Exposed through the API. | 25 import marshal # Exposed through the API. |
25 import multiprocessing | 26 import multiprocessing |
26 import optparse | 27 import optparse |
27 import os # Somewhat exposed through the API. | 28 import os # Somewhat exposed through the API. |
28 import pickle # Exposed through the API. | 29 import pickle # Exposed through the API. |
29 import random | 30 import random |
30 import re # Exposed through the API. | 31 import re # Exposed through the API. |
31 import sys # Parts exposed through API. | 32 import sys # Parts exposed through API. |
(...skipping 968 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1000 results.append(p) | 1001 results.append(p) |
1001 | 1002 |
1002 logging.debug('Presubmit files: %s' % ','.join(results)) | 1003 logging.debug('Presubmit files: %s' % ','.join(results)) |
1003 return results | 1004 return results |
1004 | 1005 |
1005 | 1006 |
1006 class GetTrySlavesExecuter(object): | 1007 class GetTrySlavesExecuter(object): |
1007 @staticmethod | 1008 @staticmethod |
1008 def ExecPresubmitScript(script_text, presubmit_path, project, change): | 1009 def ExecPresubmitScript(script_text, presubmit_path, project, change): |
1009 """Executes GetPreferredTrySlaves() from a single presubmit script. | 1010 """Executes GetPreferredTrySlaves() from a single presubmit script. |
| 1011 |
| 1012 This will soon be deprecated and replaced by GetPreferredTryMasters(). |
1010 | 1013 |
1011 Args: | 1014 Args: |
1012 script_text: The text of the presubmit script. | 1015 script_text: The text of the presubmit script. |
1013 presubmit_path: Project script to run. | 1016 presubmit_path: Project script to run. |
1014 project: Project name to pass to presubmit script for bot selection. | 1017 project: Project name to pass to presubmit script for bot selection. |
1015 | 1018 |
1016 Return: | 1019 Return: |
1017 A list of try slaves. | 1020 A list of try slaves. |
1018 """ | 1021 """ |
1019 context = {} | 1022 context = {} |
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1067 ) | 1070 ) |
1068 | 1071 |
1069 # Ensure it's either all old-style or all new-style. | 1072 # Ensure it's either all old-style or all new-style. |
1070 if not valid_oldstyle(result) and not valid_newstyle(result): | 1073 if not valid_oldstyle(result) and not valid_newstyle(result): |
1071 raise PresubmitFailure( | 1074 raise PresubmitFailure( |
1072 'PRESUBMIT.py returned invalid trybot specification!') | 1075 'PRESUBMIT.py returned invalid trybot specification!') |
1073 | 1076 |
1074 return result | 1077 return result |
1075 | 1078 |
1076 | 1079 |
| 1080 class GetTryMastersExecuter(object): |
| 1081 @staticmethod |
| 1082 def ExecPresubmitScript(script_text, presubmit_path, project, change): |
| 1083 """Executes GetPreferredTryMasters() from a single presubmit script. |
| 1084 |
| 1085 Args: |
| 1086 script_text: The text of the presubmit script. |
| 1087 presubmit_path: Project script to run. |
| 1088 project: Project name to pass to presubmit script for bot selection. |
| 1089 |
| 1090 Return: |
| 1091 A map of try masters to map of builders to set of tests. |
| 1092 """ |
| 1093 context = {} |
| 1094 try: |
| 1095 exec script_text in context |
| 1096 except Exception, e: |
| 1097 raise PresubmitFailure('"%s" had an exception.\n%s' |
| 1098 % (presubmit_path, e)) |
| 1099 |
| 1100 function_name = 'GetPreferredTryMasters' |
| 1101 if function_name not in context: |
| 1102 return {} |
| 1103 get_preferred_try_masters = context[function_name] |
| 1104 if not len(inspect.getargspec(get_preferred_try_masters)[0]) == 2: |
| 1105 raise PresubmitFailure( |
| 1106 'Expected function "GetPreferredTryMasters" to take two arguments.') |
| 1107 return get_preferred_try_masters(project, change) |
| 1108 |
| 1109 |
1077 def DoGetTrySlaves(change, | 1110 def DoGetTrySlaves(change, |
1078 changed_files, | 1111 changed_files, |
1079 repository_root, | 1112 repository_root, |
1080 default_presubmit, | 1113 default_presubmit, |
1081 project, | 1114 project, |
1082 verbose, | 1115 verbose, |
1083 output_stream): | 1116 output_stream): |
1084 """Get the list of try servers from the presubmit scripts. | 1117 """Get the list of try servers from the presubmit scripts (deprecated). |
1085 | 1118 |
1086 Args: | 1119 Args: |
1087 changed_files: List of modified files. | 1120 changed_files: List of modified files. |
1088 repository_root: The repository root. | 1121 repository_root: The repository root. |
1089 default_presubmit: A default presubmit script to execute in any case. | 1122 default_presubmit: A default presubmit script to execute in any case. |
1090 project: Optional name of a project used in selecting trybots. | 1123 project: Optional name of a project used in selecting trybots. |
1091 verbose: Prints debug info. | 1124 verbose: Prints debug info. |
1092 output_stream: A stream to write debug output to. | 1125 output_stream: A stream to write debug output to. |
1093 | 1126 |
1094 Return: | 1127 Return: |
(...skipping 30 matching lines...) Expand all Loading... |
1125 slaves = list(slave_dict.items()) | 1158 slaves = list(slave_dict.items()) |
1126 | 1159 |
1127 slaves.extend(set(old_style)) | 1160 slaves.extend(set(old_style)) |
1128 | 1161 |
1129 if slaves and verbose: | 1162 if slaves and verbose: |
1130 output_stream.write(', '.join((str(x) for x in slaves))) | 1163 output_stream.write(', '.join((str(x) for x in slaves))) |
1131 output_stream.write('\n') | 1164 output_stream.write('\n') |
1132 return slaves | 1165 return slaves |
1133 | 1166 |
1134 | 1167 |
| 1168 def _MergeMasters(masters1, masters2): |
| 1169 """Merges two master maps. Merges also the tests of each builder.""" |
| 1170 result = {} |
| 1171 for (master, builders) in itertools.chain(masters1.iteritems(), |
| 1172 masters2.iteritems()): |
| 1173 new_builders = result.setdefault(master, {}) |
| 1174 for (builder, tests) in builders.iteritems(): |
| 1175 new_builders.setdefault(builder, set([])).update(tests) |
| 1176 return result |
| 1177 |
| 1178 |
| 1179 def DoGetTryMasters(change, |
| 1180 changed_files, |
| 1181 repository_root, |
| 1182 default_presubmit, |
| 1183 project, |
| 1184 verbose, |
| 1185 output_stream): |
| 1186 """Get the list of try masters from the presubmit scripts. |
| 1187 |
| 1188 Args: |
| 1189 changed_files: List of modified files. |
| 1190 repository_root: The repository root. |
| 1191 default_presubmit: A default presubmit script to execute in any case. |
| 1192 project: Optional name of a project used in selecting trybots. |
| 1193 verbose: Prints debug info. |
| 1194 output_stream: A stream to write debug output to. |
| 1195 |
| 1196 Return: |
| 1197 Map of try masters to map of builders to set of tests. |
| 1198 """ |
| 1199 presubmit_files = ListRelevantPresubmitFiles(changed_files, repository_root) |
| 1200 if not presubmit_files and verbose: |
| 1201 output_stream.write("Warning, no PRESUBMIT.py found.\n") |
| 1202 results = {} |
| 1203 executer = GetTryMastersExecuter() |
| 1204 |
| 1205 if default_presubmit: |
| 1206 if verbose: |
| 1207 output_stream.write("Running default presubmit script.\n") |
| 1208 fake_path = os.path.join(repository_root, 'PRESUBMIT.py') |
| 1209 results = _MergeMasters(results, executer.ExecPresubmitScript( |
| 1210 default_presubmit, fake_path, project, change)) |
| 1211 for filename in presubmit_files: |
| 1212 filename = os.path.abspath(filename) |
| 1213 if verbose: |
| 1214 output_stream.write("Running %s\n" % filename) |
| 1215 # Accept CRLF presubmit script. |
| 1216 presubmit_script = gclient_utils.FileRead(filename, 'rU') |
| 1217 results = _MergeMasters(results, executer.ExecPresubmitScript( |
| 1218 presubmit_script, filename, project, change)) |
| 1219 |
| 1220 # Make sets to lists again for later JSON serialization. |
| 1221 for builders in results.itervalues(): |
| 1222 for builder in builders: |
| 1223 builders[builder] = list(builders[builder]) |
| 1224 |
| 1225 if results and verbose: |
| 1226 output_stream.write('%s\n' % str(results)) |
| 1227 return results |
| 1228 |
| 1229 |
1135 class PresubmitExecuter(object): | 1230 class PresubmitExecuter(object): |
1136 def __init__(self, change, committing, rietveld_obj, verbose): | 1231 def __init__(self, change, committing, rietveld_obj, verbose): |
1137 """ | 1232 """ |
1138 Args: | 1233 Args: |
1139 change: The Change object. | 1234 change: The Change object. |
1140 committing: True if 'gcl commit' is running, False if 'gcl upload' is. | 1235 committing: True if 'gcl commit' is running, False if 'gcl upload' is. |
1141 rietveld_obj: rietveld.Rietveld client object. | 1236 rietveld_obj: rietveld.Rietveld client object. |
1142 """ | 1237 """ |
1143 self.change = change | 1238 self.change = change |
1144 self.committing = committing | 1239 self.committing = committing |
(...skipping 363 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1508 except PresubmitFailure, e: | 1603 except PresubmitFailure, e: |
1509 print >> sys.stderr, e | 1604 print >> sys.stderr, e |
1510 print >> sys.stderr, 'Maybe your depot_tools is out of date?' | 1605 print >> sys.stderr, 'Maybe your depot_tools is out of date?' |
1511 print >> sys.stderr, 'If all fails, contact maruel@' | 1606 print >> sys.stderr, 'If all fails, contact maruel@' |
1512 return 2 | 1607 return 2 |
1513 | 1608 |
1514 | 1609 |
1515 if __name__ == '__main__': | 1610 if __name__ == '__main__': |
1516 fix_encoding.fix_encoding() | 1611 fix_encoding.fix_encoding() |
1517 sys.exit(Main(None)) | 1612 sys.exit(Main(None)) |
OLD | NEW |