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