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

Side by Side Diff: recipe_engine/run.py

Issue 2415793003: Setup basic Runtime with properties and platform.
Patch Set: Created 4 years, 2 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
OLDNEW
1 # Copyright 2016 The LUCI Authors. All rights reserved. 1 # Copyright 2016 The LUCI Authors. All rights reserved.
2 # Use of this source code is governed under the Apache License, Version 2.0 2 # Use of this source code is governed under the Apache License, Version 2.0
3 # that can be found in the LICENSE file. 3 # that can be found in the LICENSE file.
4 4
5 """Entry point for fully-annotated builds. 5 """Entry point for fully-annotated builds.
6 6
7 This script is part of the effort to move all builds to annotator-based 7 This script is part of the effort to move all builds to annotator-based
8 systems. Any builder configured to use the AnnotatorFactory.BaseFactory() 8 systems. Any builder configured to use the AnnotatorFactory.BaseFactory()
9 found in scripts/master/factory/annotator_factory.py executes a single 9 found in scripts/master/factory/annotator_factory.py executes a single
10 AddAnnotatedScript step. That step (found in annotator_commands.py) calls 10 AddAnnotatedScript step. That step (found in annotator_commands.py) calls
(...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after
59 convenience functions defined: 59 convenience functions defined:
60 * last_step - Returns the last step that ran or None 60 * last_step - Returns the last step that ran or None
61 * nth_step(n) - Returns the N'th step that ran or None 61 * nth_step(n) - Returns the N'th step that ran or None
62 62
63 'failed' is a boolean representing if the build is in a 'failed' state. 63 'failed' is a boolean representing if the build is in a 'failed' state.
64 """ 64 """
65 65
66 import collections 66 import collections
67 import json 67 import json
68 import os 68 import os
69 import sys
70 import traceback 69 import traceback
71 70
72 from . import env 71 from . import env
73 72
74 from . import loader 73 from . import loader
75 from . import recipe_api 74 from . import recipe_api
76 from . import recipe_test_api 75 from . import recipe_test_api
77 from . import step_runner as step_runner_module 76 from . import step_runner as step_runner_module
78 from . import types 77 from . import types
79 from . import util 78 from . import util
(...skipping 101 matching lines...) Expand 10 before | Expand all | Expand 10 after
181 180
182 181
183 # Return value of run_steps and RecipeEngine.run. Just a container for the 182 # Return value of run_steps and RecipeEngine.run. Just a container for the
184 # literal return value of the recipe. 183 # literal return value of the recipe.
185 RecipeResult = collections.namedtuple('RecipeResult', 'result') 184 RecipeResult = collections.namedtuple('RecipeResult', 'result')
186 185
187 186
188 # TODO(dnj): Replace "properties" with a generic runtime instance. This instance 187 # TODO(dnj): Replace "properties" with a generic runtime instance. This instance
189 # will be used to seed recipe clients and expanded to include managed runtime 188 # will be used to seed recipe clients and expanded to include managed runtime
190 # entities. 189 # entities.
191 def run_steps(properties, stream_engine, step_runner, universe_view): 190 def run_steps(rt, stream_engine, step_runner, universe_view):
192 """Runs a recipe (given by the 'recipe' property). 191 """Runs a recipe (given by the 'recipe' property).
193 192
194 Args: 193 Args:
195 properties: a dictionary of properties to pass to the recipe. The 194 rt (Runtime): The runtime instance.
196 'recipe' property defines which recipe to actually run.
197 stream_engine: the StreamEngine to use to create individual step streams. 195 stream_engine: the StreamEngine to use to create individual step streams.
198 step_runner: The StepRunner to use to 'actually run' the steps. 196 step_runner: The StepRunner to use to 'actually run' the steps.
199 universe_view: The RecipeUniverse to use to load the recipes & modules. 197 universe_view: The RecipeUniverse to use to load the recipes & modules.
200 198
201 Returns: RecipeResult 199 Returns: RecipeResult
202 """ 200 """
203 # NOTE(iannucci): 'root' was a terribly bad idea and has been replaced by
dnj 2016/10/13 01:08:23 Talked with iannucci@, probably don't need this.
204 # 'patch_project'. 'root' had Rietveld knowing about the implementation of
205 # the builders. 'patch_project' lets the builder (recipe) decide its own
206 # destiny.
207 properties.pop('root', None)
208
209 # TODO(iannucci): A much better way to do this would be to dynamically
dnj 2016/10/13 01:08:23 Transplanted into "recipes.py", closer to other pr
210 # detect if the mirrors are actually available during the execution of the
211 # recipe.
212 if ('use_mirror' not in properties and (
213 'TESTING_MASTERNAME' in os.environ or
214 'TESTING_SLAVENAME' in os.environ)):
215 properties['use_mirror'] = False
216
217 with stream_engine.make_step_stream('setup_build') as s: 201 with stream_engine.make_step_stream('setup_build') as s:
218 engine = RecipeEngine(step_runner, properties, universe_view) 202 engine = RecipeEngine(step_runner, rt, universe_view)
219 203
220 # Create all API modules and top level RunSteps function. It doesn't launch 204 # Create all API modules and top level RunSteps function. It doesn't launch
221 # any recipe code yet; RunSteps needs to be called. 205 # any recipe code yet; RunSteps needs to be called.
222 api = None 206 api = None
223 207
224 assert 'recipe' in properties 208 assert 'recipe' in rt.properties
225 recipe = properties['recipe'] 209 recipe = rt.properties['recipe']
226 210
227 properties_to_print = properties.copy() 211 # Remove domain-specific properties.
228 if 'use_mirror' in properties: 212 properties_to_print = rt.properties.copy()
229 del properties_to_print['use_mirror'] 213 properties_to_print.pop('use_mirror', None)
iannucci 2016/10/15 00:39:45 I would ditch this
dnj 2016/10/15 00:59:27 altogether, or this specific change?
iannucci 2016/10/15 01:04:09 Well you're moving the other property stuff in thi
dnj 2016/10/15 15:42:05 Done.
230 214
231 root_package = universe_view.universe.package_deps.root_package 215 root_package = universe_view.universe.package_deps.root_package
232 run_recipe_help_lines = [ 216 run_recipe_help_lines = [
233 'To repro this locally, run the following line from the root of a %r' 217 'To repro this locally, run the following line from the root of a %r'
234 ' checkout:' % (root_package.name), 218 ' checkout:' % (root_package.name),
235 '', 219 '',
236 '%s run --properties-file - %s <<EOF' % ( 220 '%s run --properties-file - %s <<EOF' % (
237 os.path.join( '.', root_package.relative_recipes_dir, 'recipes.py'), 221 os.path.join( '.', root_package.relative_recipes_dir, 'recipes.py'),
238 recipe), 222 recipe),
239 '%s' % json.dumps(properties_to_print), 223 '%s' % json.dumps(properties_to_print),
240 'EOF', 224 'EOF',
241 '', 225 '',
242 'To run on Windows, you can put the JSON in a file and redirect the', 226 'To run on Windows, you can put the JSON in a file and redirect the',
243 'contents of the file into run_recipe.py, with the < operator.', 227 'contents of the file into run_recipe.py, with the < operator.',
244 ] 228 ]
245 229
246 with s.new_log_stream('run_recipe') as l: 230 with s.new_log_stream('run_recipe') as l:
247 for line in run_recipe_help_lines: 231 for line in run_recipe_help_lines:
248 l.write_line(line) 232 l.write_line(line)
249 233
250 _isolate_environment() 234 os.environ = _isolate_environment(rt, os.environ)
251 235
252 # Find and load the recipe to run. 236 # Find and load the recipe to run.
253 try: 237 try:
254 recipe_script = universe_view.load_recipe(recipe, engine=engine) 238 recipe_script = universe_view.load_recipe(recipe, engine=engine)
255 s.write_line('Running recipe with %s' % (properties,)) 239 s.write_line('Running recipe with %s' % (rt.properties,))
256 240
257 api = loader.create_recipe_api(recipe_script.LOADED_DEPS, 241 api = loader.create_recipe_api(recipe_script.LOADED_DEPS,
258 engine, 242 engine,
259 recipe_test_api.DisabledTestData()) 243 recipe_test_api.DisabledTestData())
260 244
261 s.add_step_text('running recipe: "%s"' % recipe) 245 s.add_step_text('running recipe: "%s"' % recipe)
262 except (loader.LoaderError, ImportError, AssertionError) as e: 246 except (loader.LoaderError, ImportError, AssertionError) as e:
263 for line in str(e).splitlines(): 247 for line in str(e).splitlines():
264 s.add_step_text(line) 248 s.add_step_text(line)
265 s.set_step_status('EXCEPTION') 249 s.set_step_status('EXCEPTION')
266 return RecipeResult({ 250 return RecipeResult({
267 'status_code': 2, 251 'status_code': 2,
268 'reason': str(e), 252 'reason': str(e),
269 }) 253 })
270 254
271 # Run the steps emitted by a recipe via the engine, emitting annotations 255 # Run the steps emitted by a recipe via the engine, emitting annotations
272 # into |stream| along the way. 256 # into |stream| along the way.
273 return engine.run(recipe_script, api, properties) 257 return engine.run(recipe_script, api, rt.properties)
274 258
275 259
276 def _isolate_environment(): 260 def _isolate_environment(rt, env):
277 """Isolate the environment to a known subset set.""" 261 """Isolate the environment to a known subset set."""
278 if sys.platform.startswith('win'): 262 if rt.platform.is_win():
279 whitelist = ENV_WHITELIST_WIN 263 whitelist = ENV_WHITELIST_WIN
280 elif sys.platform in ('darwin', 'posix', 'linux2'): 264 elif rt.platform.is_posix():
281 whitelist = ENV_WHITELIST_POSIX 265 whitelist = ENV_WHITELIST_POSIX
282 else: 266 else:
283 print ('WARNING: unknown platform %s, not isolating environment.' % 267 print 'WARNING: unknown platform %s, not isolating environment.' % (
284 sys.platform) 268 rt.platform,)
285 return 269 return env
270 return {k: v for k, v in env.iteritems() if k in whitelist}
286 271
287 for k in os.environ.keys(): 272
288 if k not in whitelist: 273 class Runtime(object):
289 del os.environ[k] 274 """Container for instance-global state."""
275
276 def __init__(self, properties, platform=None):
277 self._properties = properties
278 self._platform = platform if platform else util.Platform.probe()
279
280 @property
281 def properties(self):
282 """Returns (dict): The input properties."""
283 return self._properties
284
285 @property
286 def platform(self):
287 """Returns (util.Platform): The current running Platform."""
288 return self._platform
290 289
291 290
292 class RecipeEngine(object): 291 class RecipeEngine(object):
293 """ 292 """
294 Knows how to execute steps emitted by a recipe, holds global state such as 293 Knows how to execute steps emitted by a recipe, holds global state such as
295 step history and build properties. Each recipe module API has a reference to 294 step history and build properties. Each recipe module API has a reference to
296 this object. 295 this object.
297
298 Recipe modules that are aware of the engine:
299 * properties - uses engine.properties.
300 * step - uses engine.create_step(...), and previous_step_result.
301 """ 296 """
302 297
303 ActiveStep = collections.namedtuple('ActiveStep', ( 298 ActiveStep = collections.namedtuple('ActiveStep', (
304 'config', 'step_result', 'open_step')) 299 'config', 'step_result', 'open_step'))
305 300
306 def __init__(self, step_runner, properties, universe_view): 301 def __init__(self, step_runner, rt, universe_view):
307 """See run_steps() for parameter meanings.""" 302 """See run_steps() for parameter meanings."""
308 self._step_runner = step_runner 303 self._step_runner = step_runner
309 self._properties = properties 304 self._rt = rt
310 self._universe_view = universe_view 305 self._universe_view = universe_view
311 self._clients = {client.IDENT: client for client in ( 306 self._clients = {client.IDENT: client for client in (
312 recipe_api.StepClient(self), 307 recipe_api.StepClient(self),
313 recipe_api.PropertiesClient(self), 308 recipe_api.PropertiesClient(self._rt.properties),
309 recipe_api.PlatformClient(self._rt.platform),
314 recipe_api.DependencyManagerClient(self), 310 recipe_api.DependencyManagerClient(self),
315 )} 311 )}
316 312
317 # A stack of ActiveStep objects, holding the most recently executed step at 313 # A stack of ActiveStep objects, holding the most recently executed step at
318 # each nest level (objects deeper in the stack have lower nest levels). 314 # each nest level (objects deeper in the stack have lower nest levels).
319 # When we pop from this stack, we close the corresponding step stream. 315 # When we pop from this stack, we close the corresponding step stream.
320 self._step_stack = [] 316 self._step_stack = []
321 317
322 # TODO(iannucci): come up with a more structured way to advertise/set mode 318 # TODO(iannucci): come up with a more structured way to advertise/set mode
iannucci 2016/10/15 00:39:45 I think this isn't used any more. you can delete t
dnj 2016/10/15 00:59:27 Done.
323 # flags/options for the engine. 319 # flags/options for the engine.
324 if '$recipe_engine' in properties: 320 options = self._rt.properties.get('$recipe_engine')
325 options = properties['$recipe_engine'] 321 if options is not None:
326 try: 322 try:
327 mode_flags = options.get('mode_flags') 323 mode_flags = options.get('mode_flags')
328 if mode_flags: 324 if mode_flags:
329 if mode_flags.get('use_subprocess42'): 325 if mode_flags.get('use_subprocess42'):
330 print "IGNORING MODE_SUBPROCESS42" 326 print "IGNORING MODE_SUBPROCESS42"
331 except Exception as e: 327 except Exception as e:
332 print "Failed to set recipe_engine options, got: %r: %s" % (options, e) 328 print "Failed to set recipe_engine options, got: %r: %s" % (options, e)
333 329
334 @property 330 @property
335 def properties(self): 331 def properties(self):
336 return self._properties 332 return self._rt.properties
337 333
338 @property 334 @property
339 def universe(self): 335 def universe(self):
340 return self._universe_view.universe 336 return self._universe_view.universe
341 337
342 def _close_through_level(self, level): 338 def _close_through_level(self, level):
343 """Close all open steps whose nest level is >= the supplied level. 339 """Close all open steps whose nest level is >= the supplied level.
344 340
345 Args: 341 Args:
346 level (int): the nest level to close through. 342 level (int): the nest level to close through.
(...skipping 143 matching lines...) Expand 10 before | Expand all | Expand 10 after
490 results.append( 486 results.append(
491 loader._invoke_with_properties( 487 loader._invoke_with_properties(
492 run_recipe, properties, recipe_script.PROPERTIES, 488 run_recipe, properties, recipe_script.PROPERTIES,
493 properties.keys())) 489 properties.keys()))
494 except TypeError as e: 490 except TypeError as e:
495 raise TypeError( 491 raise TypeError(
496 "Got %r while trying to call recipe %s with properties %r" % ( 492 "Got %r while trying to call recipe %s with properties %r" % (
497 e, recipe, properties)) 493 e, recipe, properties))
498 494
499 return results 495 return results
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698