OLD | NEW |
---|---|
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # Copyright (c) 2013 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2013 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 """Entry point for fully-annotated builds. | 6 """Entry point for fully-annotated builds. |
7 | 7 |
8 This script is part of the effort to move all builds to annotator-based | 8 This script is part of the effort to move all builds to annotator-based |
9 systems. Any builder configured to use the AnnotatorFactory.BaseFactory() | 9 systems. Any builder configured to use the AnnotatorFactory.BaseFactory() |
10 found in scripts/master/factory/annotator_factory.py executes a single | 10 found in scripts/master/factory/annotator_factory.py executes a single |
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
48 import subprocess | 48 import subprocess |
49 import sys | 49 import sys |
50 import tempfile | 50 import tempfile |
51 | 51 |
52 from common import annotator | 52 from common import annotator |
53 from common import chromium_utils | 53 from common import chromium_utils |
54 from slave import recipe_util | 54 from slave import recipe_util |
55 from slave import annotated_checkout | 55 from slave import annotated_checkout |
56 | 56 |
57 SCRIPT_PATH = os.path.dirname(os.path.abspath(__file__)) | 57 SCRIPT_PATH = os.path.dirname(os.path.abspath(__file__)) |
58 BUILD_ROOT = os.path.dirname(os.path.dirname(SCRIPT_PATH)) | |
58 | 59 |
59 | 60 |
60 @contextlib.contextmanager | 61 @contextlib.contextmanager |
61 def temp_purge_path(path): | 62 def temp_purge_path(path): |
62 saved = sys.path | 63 saved = sys.path |
63 sys.path = [path] | 64 sys.path = [path] |
64 try: | 65 try: |
65 yield | 66 yield |
66 finally: | 67 finally: |
67 sys.path = saved | 68 sys.path = saved |
68 | 69 |
69 | 70 |
70 def expand_root_placeholder(root, lst): | 71 def expand_root_placeholder(root, lst): |
71 """This expands CheckoutRootPlaceholder in paths to a real path. | 72 """This expands CheckoutRootPlaceholder in paths to a real path. |
72 See recipe_util.checkout_path() for usage.""" | 73 See recipe_util.checkout_path() for usage.""" |
73 ret = [] | 74 ret = [] |
74 replacements = {'CheckoutRootPlaceholder': root} | 75 replacements = {'CheckoutRootPlaceholder': root} |
75 for item in lst: | 76 for item in lst: |
76 if isinstance(item, str): | 77 if isinstance(item, str): |
77 if '%(CheckoutRootPlaceholder)s' in item: | 78 if '%(CheckoutRootPlaceholder)s' in item: |
78 assert root, 'Must use "checkout" key to use checkout_path().' | 79 assert root, 'Must use "checkout" key to use checkout_path().' |
79 ret.append(item % replacements) | 80 ret.append(item % replacements) |
80 continue | 81 continue |
81 ret.append(item) | 82 ret.append(item) |
82 return ret | 83 return ret |
83 | 84 |
84 | 85 |
85 def get_args(): | 86 def get_args(argv): |
86 """Process command-line arguments.""" | 87 """Process command-line arguments.""" |
87 | 88 |
88 parser = optparse.OptionParser( | 89 parser = optparse.OptionParser( |
89 description='Entry point for annotated builds.') | 90 description='Entry point for annotated builds.') |
90 parser.add_option('--build-properties', | 91 parser.add_option('--build-properties', |
91 action='callback', callback=chromium_utils.convert_json, | 92 action='callback', callback=chromium_utils.convert_json, |
92 type='string', default={}, | 93 type='string', default={}, |
93 help='build properties in JSON format') | 94 help='build properties in JSON format') |
94 parser.add_option('--factory-properties', | 95 parser.add_option('--factory-properties', |
95 action='callback', callback=chromium_utils.convert_json, | 96 action='callback', callback=chromium_utils.convert_json, |
96 type='string', default={}, | 97 type='string', default={}, |
97 help='factory properties in JSON format') | 98 help='factory properties in JSON format') |
98 parser.add_option('--output-build-properties', action='store_true', | |
99 help='output JSON-encoded build properties extracted from' | |
100 ' the build') | |
101 parser.add_option('--output-factory-properties', action='store_true', | |
102 help='output JSON-encoded factory properties extracted from' | |
103 'the build factory') | |
104 parser.add_option('--keep-stdin', action='store_true', default=False, | 99 parser.add_option('--keep-stdin', action='store_true', default=False, |
105 help='don\'t close stdin when running recipe steps') | 100 help='don\'t close stdin when running recipe steps') |
106 return parser.parse_args() | 101 return parser.parse_args(argv) |
107 | 102 |
108 | 103 |
109 def main(): | 104 def main(argv=None): |
110 opts, _ = get_args() | 105 opts, _ = get_args(argv) |
111 | 106 |
107 stream = annotator.StructuredAnnotationStream(seed_steps=['setup_build']) | |
108 | |
109 ret = make_steps(stream, opts.build_properties, opts.factory_properties) | |
110 | |
111 if isinstance(ret, int): | |
Vadim Shtayura
2013/05/11 00:33:32
Maybe make 'make_steps' to return a tuple (status
iannucci
2013/05/11 04:12:27
Done. (Actually made it a namedtuple)
| |
112 return ret | |
113 else: | |
114 return run_annotator(stream, ret, opts.keep_stdin) | |
115 | |
116 def make_steps(stream, build_properties, factory_properties, | |
117 test_mode=False): | |
112 # TODO(iannucci): Stop this when blamelist becomes sane data. | 118 # TODO(iannucci): Stop this when blamelist becomes sane data. |
113 if ('blamelist_real' in opts.build_properties and | 119 if ('blamelist_real' in build_properties and |
114 'blamelist' in opts.build_properties): | 120 'blamelist' in build_properties): |
115 opts.build_properties['blamelist'] = opts.build_properties['blamelist_real'] | 121 build_properties['blamelist'] = build_properties['blamelist_real'] |
116 del opts.build_properties['blamelist_real'] | 122 del build_properties['blamelist_real'] |
117 | 123 |
118 # Supplement the master-supplied factory_properties dictionary with the values | |
119 # found in the slave-side recipe. | |
120 stream = annotator.StructuredAnnotationStream(seed_steps=['setup_build']) | |
121 with stream.step('setup_build') as s: | 124 with stream.step('setup_build') as s: |
122 assert 'recipe' in opts.factory_properties | 125 assert 'recipe' in factory_properties |
123 factory_properties = opts.factory_properties | 126 factory_properties = factory_properties |
Vadim Shtayura
2013/05/11 00:33:32
Hmm...
iannucci
2013/05/11 04:12:27
Oops. Find/replace :D
| |
124 recipe = factory_properties['recipe'] | 127 recipe = factory_properties['recipe'] |
125 recipe_dirs = (os.path.abspath(p) for p in ( | 128 recipe_dirs = (os.path.abspath(p) for p in ( |
126 os.path.join(SCRIPT_PATH, '..', '..', '..', 'build_internal', 'scripts', | 129 os.path.join(SCRIPT_PATH, '..', '..', '..', 'build_internal', 'scripts', |
127 'slave-internal', 'recipes'), | 130 'slave-internal', 'recipes'), |
128 os.path.join(SCRIPT_PATH, '..', '..', '..', 'build_internal', 'scripts', | 131 os.path.join(SCRIPT_PATH, '..', '..', '..', 'build_internal', 'scripts', |
129 'slave', 'recipes'), | 132 'slave', 'recipes'), |
130 os.path.join(SCRIPT_PATH, 'recipes'), | 133 os.path.join(SCRIPT_PATH, 'recipes'), |
131 )) | 134 )) |
132 | 135 |
133 for path in recipe_dirs: | 136 for path in recipe_dirs: |
134 recipe_module = None | 137 recipe_module = None |
135 with temp_purge_path(path): | 138 with temp_purge_path(path): |
136 try: | 139 try: |
137 recipe_module = __import__(recipe, globals(), locals()) | 140 recipe_module = __import__(recipe, globals(), locals()) |
138 except ImportError: | 141 except ImportError: |
139 continue | 142 continue |
140 recipe_dict = recipe_module.GetFactoryProperties( | 143 recipe_dict = recipe_module.GetFactoryProperties( |
141 recipe_util, | 144 recipe_util, |
142 opts.factory_properties.copy(), | 145 factory_properties.copy(), |
143 opts.build_properties.copy()) | 146 build_properties.copy()) |
144 break | 147 break |
145 else: | 148 else: |
146 s.step_text('recipe not found') | 149 s.step_text('recipe not found') |
147 s.step_failure() | 150 s.step_failure() |
148 return 1 | 151 return 1 |
149 | 152 |
150 factory_properties.update(recipe_dict) | 153 factory_properties.update(recipe_dict) |
151 | 154 |
152 # If a checkout is specified, get its type and spec and pass them | 155 # If a checkout is specified, get its type and spec and pass them |
153 # off to annotated_checkout.py to actually fetch the repo. | 156 # off to annotated_checkout.py to actually fetch the repo. |
154 # annotated_checkout.py handles its own StructuredAnnotationStream. | 157 # annotated_checkout.py handles its own StructuredAnnotationStream. |
155 root = None | 158 root = None |
156 if 'checkout' in factory_properties: | 159 if 'checkout' in factory_properties: |
157 checkout_type = factory_properties['checkout'] | 160 checkout_type = factory_properties['checkout'] |
158 checkout_spec = factory_properties['%s_spec' % checkout_type] | 161 checkout_spec = factory_properties['%s_spec' % checkout_type] |
159 ret, root = annotated_checkout.run(checkout_type, checkout_spec) | 162 ret, root = annotated_checkout.run(checkout_type, checkout_spec, |
163 test_mode) | |
160 if ret != 0: | 164 if ret != 0: |
161 return ret | 165 return ret |
166 if test_mode: | |
167 root = '[BUILD_ROOT]'+root[len(BUILD_ROOT):] | |
162 | 168 |
163 assert ('script' in factory_properties) ^ ('steps' in factory_properties) | 169 assert ('script' in factory_properties) ^ ('steps' in factory_properties) |
164 ret = 0 | 170 ret = 0 |
165 | 171 |
166 # If a script is specified, import it, execute its GetSteps method, | 172 # If a script is specified, import it, execute its GetSteps method, |
167 # and pass those steps forward so they get executed by annotator.py. | 173 # and pass those steps forward so they get executed by annotator.py. |
174 # If we're in test_mode mode, just return the script. | |
168 if 'script' in factory_properties: | 175 if 'script' in factory_properties: |
169 with stream.step('get_steps') as s: | 176 with stream.step('get_steps') as s: |
170 assert isinstance(factory_properties['script'], str) | 177 assert isinstance(factory_properties['script'], str) |
171 [script] = expand_root_placeholder(root, [factory_properties['script']]) | 178 [script] = expand_root_placeholder(root, [factory_properties['script']]) |
179 if test_mode: | |
180 return script | |
172 assert os.path.abspath(script) == script | 181 assert os.path.abspath(script) == script |
182 | |
173 with temp_purge_path(os.path.dirname(script)): | 183 with temp_purge_path(os.path.dirname(script)): |
174 try: | 184 try: |
175 script_name = os.path.splitext(os.path.basename(script))[0] | 185 script_name = os.path.splitext(os.path.basename(script))[0] |
176 script_module = __import__(script_name, globals(), locals()) | 186 script_module = __import__(script_name, globals(), locals()) |
177 except ImportError: | 187 except ImportError: |
178 s.step_text('script not found') | 188 s.step_text('script not found') |
179 s.step_failure() | 189 s.step_failure() |
180 return 1 | 190 return 1 |
181 steps_dict = script_module.GetSteps(recipe_util, | 191 steps_dict = script_module.GetSteps(recipe_util, |
182 opts.factory_properties.copy(), | 192 factory_properties.copy(), |
183 opts.build_properties.copy()) | 193 build_properties.copy()) |
184 factory_properties['steps'] = steps_dict | 194 factory_properties['steps'] = steps_dict |
185 | 195 |
186 # Execute annotator.py with steps if specified. | 196 # Execute annotator.py with steps if specified. |
187 # annotator.py handles the seeding, execution, and annotation of each step. | 197 # annotator.py handles the seeding, execution, and annotation of each step. |
188 if 'steps' in factory_properties: | 198 if 'steps' in factory_properties: |
189 steps = factory_properties.pop('steps') | 199 steps = factory_properties.pop('steps') |
190 factory_properties_str = json.dumps(factory_properties) | 200 factory_properties_str = json.dumps(factory_properties) |
191 build_properties_str = json.dumps(opts.build_properties) | 201 build_properties_str = json.dumps(build_properties) |
192 property_placeholder_lst = [ | 202 property_placeholder_lst = [ |
193 '--factory-properties', factory_properties_str, | 203 '--factory-properties', factory_properties_str, |
194 '--build-properties', build_properties_str] | 204 '--build-properties', build_properties_str] |
195 for step in steps: | 205 for step in steps: |
196 new_cmd = [] | 206 new_cmd = [] |
197 for item in expand_root_placeholder(root, step['cmd']): | 207 for item in expand_root_placeholder(root, step['cmd']): |
198 if item == recipe_util.PropertyPlaceholder: | 208 if item == recipe_util.PropertyPlaceholder: |
199 new_cmd.extend(property_placeholder_lst) | 209 new_cmd.extend(property_placeholder_lst) |
200 else: | 210 else: |
201 new_cmd.append(item) | 211 new_cmd.append(item) |
202 step['cmd'] = new_cmd | 212 step['cmd'] = new_cmd |
203 if 'cwd' in step: | 213 if 'cwd' in step: |
204 [new_cwd] = expand_root_placeholder(root, [step['cwd']]) | 214 [new_cwd] = expand_root_placeholder(root, [step['cwd']]) |
205 step['cwd'] = new_cwd | 215 step['cwd'] = new_cwd |
206 annotator_path = os.path.join( | 216 |
207 os.path.dirname(SCRIPT_PATH), 'common', 'annotator.py') | 217 return steps |
208 tmpfile, tmpname = tempfile.mkstemp() | 218 |
209 try: | 219 def run_annotator(stream, steps, keep_stdin): |
210 cmd = [sys.executable, annotator_path, tmpname] | 220 ret = 0 |
211 step_doc = json.dumps(steps) | 221 annotator_path = os.path.join( |
212 with os.fdopen(tmpfile, 'wb') as f: | 222 os.path.dirname(SCRIPT_PATH), 'common', 'annotator.py') |
213 f.write(step_doc) | 223 tmpfile, tmpname = tempfile.mkstemp() |
214 with stream.step('annotator_preamble') as s: | 224 try: |
215 print 'in %s executing: %s' % (os.getcwd(), ' '.join(cmd)) | 225 cmd = [sys.executable, annotator_path, tmpname] |
216 print 'with: %s' % step_doc | 226 step_doc = json.dumps(steps) |
217 if opts.keep_stdin: | 227 with os.fdopen(tmpfile, 'wb') as f: |
218 ret = subprocess.call(cmd) | 228 f.write(step_doc) |
219 else: | 229 with stream.step('annotator_preamble'): |
220 proc = subprocess.Popen(cmd, stdin=subprocess.PIPE) | 230 print 'in %s executing: %s' % (os.getcwd(), ' '.join(cmd)) |
221 proc.communicate('') | 231 print 'with: %s' % step_doc |
222 ret = proc.returncode | 232 if keep_stdin: |
223 finally: | 233 ret = subprocess.call(cmd) |
224 os.unlink(tmpname) | 234 else: |
235 proc = subprocess.Popen(cmd, stdin=subprocess.PIPE) | |
236 proc.communicate('') | |
237 ret = proc.returncode | |
238 finally: | |
239 os.unlink(tmpname) | |
225 | 240 |
226 return ret | 241 return ret |
227 | 242 |
228 | 243 |
229 def UpdateScripts(): | 244 def UpdateScripts(): |
230 if os.environ.get('RUN_SLAVE_UPDATED_SCRIPTS'): | 245 if os.environ.get('RUN_SLAVE_UPDATED_SCRIPTS'): |
231 os.environ.pop('RUN_SLAVE_UPDATED_SCRIPTS') | 246 os.environ.pop('RUN_SLAVE_UPDATED_SCRIPTS') |
232 return False | 247 return False |
233 stream = annotator.StructuredAnnotationStream(seed_steps=['update_scripts']) | 248 stream = annotator.StructuredAnnotationStream(seed_steps=['update_scripts']) |
234 with stream.step('update_scripts') as s: | 249 with stream.step('update_scripts') as s: |
235 build_root = os.path.join(SCRIPT_PATH, '..', '..') | 250 build_root = os.path.join(SCRIPT_PATH, '..', '..') |
236 gclient_name = 'gclient' | 251 gclient_name = 'gclient' |
237 if sys.platform.startswith('win'): | 252 if sys.platform.startswith('win'): |
238 gclient_name += '.bat' | 253 gclient_name += '.bat' |
239 gclient_path = os.path.join(build_root, '..', 'depot_tools', gclient_name) | 254 gclient_path = os.path.join(build_root, '..', 'depot_tools', gclient_name) |
240 if subprocess.call([gclient_path, 'sync', '--force'], cwd=build_root) != 0: | 255 if subprocess.call([gclient_path, 'sync', '--force'], cwd=build_root) != 0: |
241 s.step_text('gclient sync failed!') | 256 s.step_text('gclient sync failed!') |
242 s.step_warnings() | 257 s.step_warnings() |
243 os.environ['RUN_SLAVE_UPDATED_SCRIPTS'] = '1' | 258 os.environ['RUN_SLAVE_UPDATED_SCRIPTS'] = '1' |
244 return True | 259 return True |
245 | 260 |
246 | 261 |
247 if __name__ == '__main__': | 262 if __name__ == '__main__': |
248 if UpdateScripts(): | 263 if UpdateScripts(): |
249 os.execv(sys.executable, [sys.executable] + sys.argv) | 264 os.execv(sys.executable, [sys.executable] + sys.argv) |
250 sys.exit(main()) | 265 sys.exit(main()) |
OLD | NEW |