Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(424)

Side by Side Diff: scripts/slave/annotated_run.py

Issue 14988009: First cut of testing infrastructure for recipes. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/build
Patch Set: Add comment Created 7 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
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
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')
agable 2013/05/14 20:47:23 Thanks for removing these.
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. This is mainly to support the
124 testing interface used by unittests/recipes_test.py. In particular, unless
125 test_mode is set, this function should never return a value for script.
126 """
127 MakeStepsRetval = namedtuple('MakeStepsRetval', 'status_code script steps')
111 128
112 # TODO(iannucci): Stop this when blamelist becomes sane data. 129 # TODO(iannucci): Stop this when blamelist becomes sane data.
113 if ('blamelist_real' in opts.build_properties and 130 if ('blamelist_real' in build_properties and
114 'blamelist' in opts.build_properties): 131 'blamelist' in build_properties):
115 opts.build_properties['blamelist'] = opts.build_properties['blamelist_real'] 132 build_properties['blamelist'] = build_properties['blamelist_real']
116 del opts.build_properties['blamelist_real'] 133 del build_properties['blamelist_real']
117 134
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: 135 with stream.step('setup_build') as s:
122 assert 'recipe' in opts.factory_properties 136 assert 'recipe' in factory_properties
123 factory_properties = opts.factory_properties
124 recipe = factory_properties['recipe'] 137 recipe = factory_properties['recipe']
125 recipe_dirs = (os.path.abspath(p) for p in ( 138 recipe_dirs = (os.path.abspath(p) for p in (
126 os.path.join(SCRIPT_PATH, '..', '..', '..', 'build_internal', 'scripts', 139 os.path.join(SCRIPT_PATH, '..', '..', '..', 'build_internal', 'scripts',
127 'slave-internal', 'recipes'), 140 'slave-internal', 'recipes'),
128 os.path.join(SCRIPT_PATH, '..', '..', '..', 'build_internal', 'scripts', 141 os.path.join(SCRIPT_PATH, '..', '..', '..', 'build_internal', 'scripts',
129 'slave', 'recipes'), 142 'slave', 'recipes'),
130 os.path.join(SCRIPT_PATH, 'recipes'), 143 os.path.join(SCRIPT_PATH, 'recipes'),
131 )) 144 ))
132 145
133 for path in recipe_dirs: 146 for path in recipe_dirs:
134 recipe_module = None 147 recipe_module = None
135 with temp_purge_path(path): 148 with temp_purge_path(path):
136 try: 149 try:
137 recipe_module = __import__(recipe, globals(), locals()) 150 recipe_module = __import__(recipe, globals(), locals())
138 except ImportError: 151 except ImportError:
139 continue 152 continue
140 recipe_dict = recipe_module.GetFactoryProperties( 153 recipe_dict = recipe_module.GetFactoryProperties(
141 recipe_util, 154 recipe_util,
142 opts.factory_properties.copy(), 155 factory_properties.copy(),
143 opts.build_properties.copy()) 156 build_properties.copy())
144 break 157 break
145 else: 158 else:
146 s.step_text('recipe not found') 159 s.step_text('recipe not found')
147 s.step_failure() 160 s.step_failure()
148 return 1 161 return MakeStepsRetval(1, None, None)
149 162
150 factory_properties.update(recipe_dict) 163 factory_properties.update(recipe_dict)
151 164
152 # If a checkout is specified, get its type and spec and pass them 165 # If a checkout is specified, get its type and spec and pass them
153 # off to annotated_checkout.py to actually fetch the repo. 166 # off to annotated_checkout.py to actually fetch the repo.
154 # annotated_checkout.py handles its own StructuredAnnotationStream. 167 # annotated_checkout.py handles its own StructuredAnnotationStream.
155 root = None 168 root = None
156 if 'checkout' in factory_properties: 169 if 'checkout' in factory_properties:
157 checkout_type = factory_properties['checkout'] 170 checkout_type = factory_properties['checkout']
158 checkout_spec = factory_properties['%s_spec' % checkout_type] 171 checkout_spec = factory_properties['%s_spec' % checkout_type]
159 ret, root = annotated_checkout.run(checkout_type, checkout_spec) 172 ret, root = annotated_checkout.run(checkout_type, checkout_spec,
173 test_mode)
160 if ret != 0: 174 if ret != 0:
161 return ret 175 return MakeStepsRetval(ret, None, None)
176 if test_mode:
177 root = '[BUILD_ROOT]'+root[len(BUILD_ROOT):]
162 178
163 assert ('script' in factory_properties) ^ ('steps' in factory_properties) 179 assert ('script' in factory_properties) ^ ('steps' in factory_properties)
164 ret = 0 180 ret = 0
165 181
166 # If a script is specified, import it, execute its GetSteps method, 182 # If a script is specified, import it, execute its GetSteps method,
167 # and pass those steps forward so they get executed by annotator.py. 183 # and pass those steps forward so they get executed by annotator.py.
184 # If we're in test_mode mode, just return the script.
168 if 'script' in factory_properties: 185 if 'script' in factory_properties:
169 with stream.step('get_steps') as s: 186 with stream.step('get_steps') as s:
170 assert isinstance(factory_properties['script'], str) 187 assert isinstance(factory_properties['script'], str)
171 [script] = expand_root_placeholder(root, [factory_properties['script']]) 188 [script] = expand_root_placeholder(root, [factory_properties['script']])
189 if test_mode:
190 return MakeStepsRetval(None, script, None)
172 assert os.path.abspath(script) == script 191 assert os.path.abspath(script) == script
192
173 with temp_purge_path(os.path.dirname(script)): 193 with temp_purge_path(os.path.dirname(script)):
174 try: 194 try:
175 script_name = os.path.splitext(os.path.basename(script))[0] 195 script_name = os.path.splitext(os.path.basename(script))[0]
176 script_module = __import__(script_name, globals(), locals()) 196 script_module = __import__(script_name, globals(), locals())
177 except ImportError: 197 except ImportError:
178 s.step_text('script not found') 198 s.step_text('script not found')
179 s.step_failure() 199 s.step_failure()
180 return 1 200 return MakeStepsRetval(1, None, None)
181 steps_dict = script_module.GetSteps(recipe_util, 201 steps_dict = script_module.GetSteps(recipe_util,
182 opts.factory_properties.copy(), 202 factory_properties.copy(),
183 opts.build_properties.copy()) 203 build_properties.copy())
184 factory_properties['steps'] = steps_dict 204 factory_properties['steps'] = steps_dict
185 205
186 # Execute annotator.py with steps if specified. 206 # Execute annotator.py with steps if specified.
187 # annotator.py handles the seeding, execution, and annotation of each step. 207 # annotator.py handles the seeding, execution, and annotation of each step.
188 if 'steps' in factory_properties: 208 if 'steps' in factory_properties:
189 steps = factory_properties.pop('steps') 209 steps = factory_properties.pop('steps')
190 factory_properties_str = json.dumps(factory_properties) 210 factory_properties_str = json.dumps(factory_properties)
191 build_properties_str = json.dumps(opts.build_properties) 211 build_properties_str = json.dumps(build_properties)
192 property_placeholder_lst = [ 212 property_placeholder_lst = [
193 '--factory-properties', factory_properties_str, 213 '--factory-properties', factory_properties_str,
194 '--build-properties', build_properties_str] 214 '--build-properties', build_properties_str]
195 for step in steps: 215 for step in steps:
196 new_cmd = [] 216 new_cmd = []
197 for item in expand_root_placeholder(root, step['cmd']): 217 for item in expand_root_placeholder(root, step['cmd']):
198 if item == recipe_util.PropertyPlaceholder: 218 if item == recipe_util.PropertyPlaceholder:
199 new_cmd.extend(property_placeholder_lst) 219 new_cmd.extend(property_placeholder_lst)
200 else: 220 else:
201 new_cmd.append(item) 221 new_cmd.append(item)
202 step['cmd'] = new_cmd 222 step['cmd'] = new_cmd
203 if 'cwd' in step: 223 if 'cwd' in step:
204 [new_cwd] = expand_root_placeholder(root, [step['cwd']]) 224 [new_cwd] = expand_root_placeholder(root, [step['cwd']])
205 step['cwd'] = new_cwd 225 step['cwd'] = new_cwd
206 annotator_path = os.path.join( 226
207 os.path.dirname(SCRIPT_PATH), 'common', 'annotator.py') 227 return MakeStepsRetval(None, None, steps)
208 tmpfile, tmpname = tempfile.mkstemp() 228
209 try: 229 def run_annotator(stream, steps, keep_stdin):
210 cmd = [sys.executable, annotator_path, tmpname] 230 ret = 0
211 step_doc = json.dumps(steps) 231 annotator_path = os.path.join(
212 with os.fdopen(tmpfile, 'wb') as f: 232 os.path.dirname(SCRIPT_PATH), 'common', 'annotator.py')
213 f.write(step_doc) 233 tmpfile, tmpname = tempfile.mkstemp()
214 with stream.step('annotator_preamble') as s: 234 try:
215 print 'in %s executing: %s' % (os.getcwd(), ' '.join(cmd)) 235 cmd = [sys.executable, annotator_path, tmpname]
216 print 'with: %s' % step_doc 236 step_doc = json.dumps(steps)
217 if opts.keep_stdin: 237 with os.fdopen(tmpfile, 'wb') as f:
218 ret = subprocess.call(cmd) 238 f.write(step_doc)
219 else: 239 with stream.step('annotator_preamble'):
220 proc = subprocess.Popen(cmd, stdin=subprocess.PIPE) 240 print 'in %s executing: %s' % (os.getcwd(), ' '.join(cmd))
221 proc.communicate('') 241 print 'with: %s' % step_doc
222 ret = proc.returncode 242 if keep_stdin:
223 finally: 243 ret = subprocess.call(cmd)
224 os.unlink(tmpname) 244 else:
245 proc = subprocess.Popen(cmd, stdin=subprocess.PIPE)
246 proc.communicate('')
247 ret = proc.returncode
248 finally:
249 os.unlink(tmpname)
225 250
226 return ret 251 return ret
227 252
228 253
229 def UpdateScripts(): 254 def UpdateScripts():
230 if os.environ.get('RUN_SLAVE_UPDATED_SCRIPTS'): 255 if os.environ.get('RUN_SLAVE_UPDATED_SCRIPTS'):
231 os.environ.pop('RUN_SLAVE_UPDATED_SCRIPTS') 256 os.environ.pop('RUN_SLAVE_UPDATED_SCRIPTS')
232 return False 257 return False
233 stream = annotator.StructuredAnnotationStream(seed_steps=['update_scripts']) 258 stream = annotator.StructuredAnnotationStream(seed_steps=['update_scripts'])
234 with stream.step('update_scripts') as s: 259 with stream.step('update_scripts') as s:
235 build_root = os.path.join(SCRIPT_PATH, '..', '..') 260 build_root = os.path.join(SCRIPT_PATH, '..', '..')
236 gclient_name = 'gclient' 261 gclient_name = 'gclient'
237 if sys.platform.startswith('win'): 262 if sys.platform.startswith('win'):
238 gclient_name += '.bat' 263 gclient_name += '.bat'
239 gclient_path = os.path.join(build_root, '..', 'depot_tools', gclient_name) 264 gclient_path = os.path.join(build_root, '..', 'depot_tools', gclient_name)
240 if subprocess.call([gclient_path, 'sync', '--force'], cwd=build_root) != 0: 265 if subprocess.call([gclient_path, 'sync', '--force'], cwd=build_root) != 0:
241 s.step_text('gclient sync failed!') 266 s.step_text('gclient sync failed!')
242 s.step_warnings() 267 s.step_warnings()
243 os.environ['RUN_SLAVE_UPDATED_SCRIPTS'] = '1' 268 os.environ['RUN_SLAVE_UPDATED_SCRIPTS'] = '1'
244 return True 269 return True
245 270
246 271
247 if __name__ == '__main__': 272 if __name__ == '__main__':
248 if UpdateScripts(): 273 if UpdateScripts():
249 os.execv(sys.executable, [sys.executable] + sys.argv) 274 os.execv(sys.executable, [sys.executable] + sys.argv)
250 sys.exit(main()) 275 sys.exit(main())
agable 2013/05/14 20:47:23 Doesn't this now need to pass sys.argv?
iannucci 2013/05/14 21:32:08 Well, it can, but doesn't technically need to, sin
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698