Chromium Code Reviews| 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?" | |
|
Mike Stip (use stip instead)
2013/05/14 18:01:32
can you put a comment here saying that script is m
iannucci
2013/05/14 19:18:14
Good point, done.
| |
| 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') | |
|
Mike Stip (use stip instead)
2013/05/14 18:01:32
didn't know about namedtuple. cool!
iannucci
2013/05/14 19:18:14
:)
| |
| 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('') | |
| 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 |