Index: recipe_engine/run.py |
diff --git a/recipe_engine/run.py b/recipe_engine/run.py |
index 4261a3cf0552ca739619ddb188de77ec47e73f94..8ead3f3268c85896db7dbb01c64f90329d7a627c 100644 |
--- a/recipe_engine/run.py |
+++ b/recipe_engine/run.py |
@@ -64,9 +64,9 @@ iterable_of_things. |
""" |
import collections |
+import copy |
import json |
import os |
-import sys |
import traceback |
from . import env |
@@ -188,12 +188,11 @@ RecipeResult = collections.namedtuple('RecipeResult', 'result') |
# TODO(dnj): Replace "properties" with a generic runtime instance. This instance |
# will be used to seed recipe clients and expanded to include managed runtime |
# entities. |
-def run_steps(properties, stream_engine, step_runner, universe_view): |
+def run_steps(rt, stream_engine, step_runner, universe_view): |
"""Runs a recipe (given by the 'recipe' property). |
Args: |
- properties: a dictionary of properties to pass to the recipe. The |
- 'recipe' property defines which recipe to actually run. |
+ rt (Runtime): The runtime instance. |
stream_engine: the StreamEngine to use to create individual step streams. |
step_runner: The StepRunner to use to 'actually run' the steps. |
universe_view: The RecipeUniverse to use to load the recipes & modules. |
@@ -201,14 +200,14 @@ def run_steps(properties, stream_engine, step_runner, universe_view): |
Returns: RecipeResult |
""" |
with stream_engine.make_step_stream('setup_build') as s: |
- engine = RecipeEngine(step_runner, properties, universe_view) |
+ engine = RecipeEngine(step_runner, rt, universe_view) |
# Create all API modules and top level RunSteps function. It doesn't launch |
# any recipe code yet; RunSteps needs to be called. |
api = None |
- assert 'recipe' in properties |
- recipe = properties['recipe'] |
+ assert 'recipe' in rt.properties |
+ recipe = rt.properties['recipe'] |
root_package = universe_view.universe.package_deps.root_package |
run_recipe_help_lines = [ |
@@ -218,7 +217,7 @@ def run_steps(properties, stream_engine, step_runner, universe_view): |
'%s run --properties-file - %s <<EOF' % ( |
os.path.join( '.', root_package.relative_recipes_dir, 'recipes.py'), |
recipe), |
- '%s' % json.dumps(properties), |
+ '%s' % rt.properties.to_json(sort_keys=True), |
'EOF', |
'', |
'To run on Windows, you can put the JSON in a file and redirect the', |
@@ -229,12 +228,12 @@ def run_steps(properties, stream_engine, step_runner, universe_view): |
for line in run_recipe_help_lines: |
l.write_line(line) |
- _isolate_environment() |
+ os.environ = _isolate_environment(rt, os.environ) |
# Find and load the recipe to run. |
try: |
recipe_script = universe_view.load_recipe(recipe, engine=engine) |
- s.write_line('Running recipe with %s' % (properties,)) |
+ s.write_line('Running recipe with %s' % (rt.properties,)) |
api = loader.create_recipe_api(recipe_script.LOADED_DEPS, |
engine, |
@@ -252,23 +251,47 @@ def run_steps(properties, stream_engine, step_runner, universe_view): |
# Run the steps emitted by a recipe via the engine, emitting annotations |
# into |stream| along the way. |
- return engine.run(recipe_script, api, properties) |
+ return engine.run(recipe_script, api) |
-def _isolate_environment(): |
+def _isolate_environment(rt, env): |
"""Isolate the environment to a known subset set.""" |
- if sys.platform.startswith('win'): |
+ if rt.platform.is_win(): |
whitelist = ENV_WHITELIST_WIN |
- elif sys.platform in ('darwin', 'posix', 'linux2'): |
+ elif rt.platform.is_posix(): |
whitelist = ENV_WHITELIST_POSIX |
else: |
- print ('WARNING: unknown platform %s, not isolating environment.' % |
- sys.platform) |
- return |
+ print 'WARNING: unknown platform %s, not isolating environment.' % ( |
+ rt.platform,) |
+ return env |
+ return {k: v for k, v in env.iteritems() if k in whitelist} |
- for k in os.environ.keys(): |
- if k not in whitelist: |
- del os.environ[k] |
+ |
+class Runtime(object): |
+ """Container for instance-global state.""" |
+ |
+ def __init__(self, properties, platform=None): |
+ # Store both the frozen and mutable properties. We will use the frozen ones |
+ # internally to ensure recipe engine code doesn't modify them. The |
+ # properties client may serve the original properties dict. |
+ self._properties = types.freeze(properties) |
+ self._properties_dict = copy.deepcopy(properties) |
+ |
+ self._platform = platform if platform else util.Platform.probe() |
+ |
+ @property |
+ def properties(self): |
+ """Returns (types.FrozenDict): The input properties.""" |
+ return self._properties |
+ |
+ def mutable_properties(self): |
+ """Returns (dict): A copy of the input properties.""" |
+ return copy.deepcopy(self._properties_dict) |
+ |
+ @property |
+ def platform(self): |
+ """Returns (util.Platform): The current running Platform.""" |
+ return self._platform |
class RecipeEngine(object): |
@@ -276,23 +299,20 @@ class RecipeEngine(object): |
Knows how to execute steps emitted by a recipe, holds global state such as |
step history and build properties. Each recipe module API has a reference to |
this object. |
- |
- Recipe modules that are aware of the engine: |
- * properties - uses engine.properties. |
- * step - uses engine.create_step(...), and previous_step_result. |
""" |
ActiveStep = collections.namedtuple('ActiveStep', ( |
'config', 'step_result', 'open_step')) |
- def __init__(self, step_runner, properties, universe_view): |
+ def __init__(self, step_runner, rt, universe_view): |
"""See run_steps() for parameter meanings.""" |
self._step_runner = step_runner |
- self._properties = properties |
+ self._rt = rt |
self._universe_view = universe_view |
self._clients = {client.IDENT: client for client in ( |
recipe_api.StepClient(self), |
- recipe_api.PropertiesClient(self), |
+ recipe_api.PropertiesClient(self._rt), |
+ recipe_api.PlatformClient(self._rt.platform), |
recipe_api.DependencyManagerClient(self), |
)} |
@@ -301,21 +321,9 @@ class RecipeEngine(object): |
# When we pop from this stack, we close the corresponding step stream. |
self._step_stack = [] |
- # TODO(iannucci): come up with a more structured way to advertise/set mode |
- # flags/options for the engine. |
- if '$recipe_engine' in properties: |
- options = properties['$recipe_engine'] |
- try: |
- mode_flags = options.get('mode_flags') |
- if mode_flags: |
- if mode_flags.get('use_subprocess42'): |
- print "IGNORING MODE_SUBPROCESS42" |
- except Exception as e: |
- print "Failed to set recipe_engine options, got: %r: %s" % (options, e) |
- |
@property |
def properties(self): |
- return self._properties |
+ return self._rt.properties |
@property |
def universe(self): |
@@ -385,7 +393,7 @@ class RecipeEngine(object): |
raise exc(step_config.name, step_result) |
- def run(self, recipe_script, api, properties): |
+ def run(self, recipe_script, api): |
"""Run a recipe represented by a recipe_script object. |
This function blocks until recipe finishes. |
@@ -396,7 +404,6 @@ class RecipeEngine(object): |
recipe_script: The recipe to run, as represented by a RecipeScript object. |
api: The api, with loaded module dependencies. |
Used by the some special modules. |
- properties: a dictionary of properties to pass to the recipe. |
Returns: |
RecipeResult which has return value or status code and exception. |
@@ -406,7 +413,7 @@ class RecipeEngine(object): |
with self._step_runner.run_context(): |
try: |
try: |
- recipe_result = recipe_script.run(api, properties) |
+ recipe_result = recipe_script.run(api, self._rt) |
result = { |
"recipe_result": recipe_result, |
"status_code": 0 |