| OLD | NEW |
| 1 # Copyright 2013 The Chromium Authors. All rights reserved. | 1 # Copyright 2013 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 import contextlib |
| 5 import imp | 6 import imp |
| 6 import inspect | 7 import inspect |
| 7 import os | 8 import os |
| 8 import sys | 9 import sys |
| 9 | 10 |
| 10 from .recipe_util import (ROOT_PATH, RECIPE_DIRS, MODULE_DIRS, | 11 from .recipe_util import scan_directory |
| 11 cached_unary, scan_directory) | |
| 12 from .recipe_api import RecipeApi, RecipeApiPlain | 12 from .recipe_api import RecipeApi, RecipeApiPlain |
| 13 from .recipe_config import ConfigContext | 13 from .recipe_config import ConfigContext |
| 14 from .recipe_config_types import Path, ModuleBasePath, RECIPE_MODULE_PREFIX | 14 from .recipe_config_types import Path, ModuleBasePath, RECIPE_MODULE_PREFIX |
| 15 from .recipe_test_api import RecipeTestApi, DisabledTestData | 15 from .recipe_test_api import RecipeTestApi, DisabledTestData |
| 16 | 16 |
| 17 | 17 |
| 18 class NoSuchRecipe(Exception): | 18 class NoSuchRecipe(Exception): |
| 19 """Raised by load_recipe is recipe is not found.""" | 19 """Raised by load_recipe is recipe is not found.""" |
| 20 | 20 |
| 21 | 21 |
| 22 class RecipeScript(object): | 22 class RecipeScript(object): |
| 23 """Holds dict of an evaluated recipe script.""" | 23 """Holds dict of an evaluated recipe script.""" |
| 24 | 24 |
| 25 def __init__(self, recipe_dict): | 25 def __init__(self, recipe_dict): |
| 26 for k, v in recipe_dict.iteritems(): | 26 for k, v in recipe_dict.iteritems(): |
| 27 setattr(self, k, v) | 27 setattr(self, k, v) |
| 28 | 28 |
| 29 @classmethod | 29 @classmethod |
| 30 def from_script_path(cls, script_path, universe): | 30 def from_script_path(cls, script_path, universe): |
| 31 """Evaluates a script and returns RecipeScript instance.""" | 31 """Evaluates a script and returns RecipeScript instance.""" |
| 32 |
| 32 script_vars = {} | 33 script_vars = {} |
| 33 execfile(script_path, script_vars) | |
| 34 script_vars['__file__'] = script_path | 34 script_vars['__file__'] = script_path |
| 35 |
| 36 with _preserve_path(): |
| 37 execfile(script_path, script_vars) |
| 38 |
| 35 script_vars['LOADED_DEPS'] = universe.deps_from_mixed( | 39 script_vars['LOADED_DEPS'] = universe.deps_from_mixed( |
| 36 script_vars.get('DEPS', []), os.path.basename(script_path)) | 40 script_vars.get('DEPS', []), os.path.basename(script_path)) |
| 37 return cls(script_vars) | 41 return cls(script_vars) |
| 38 | 42 |
| 39 | 43 |
| 40 class Dependency(object): | 44 class Dependency(object): |
| 41 def load(self, universe): | 45 def load(self, universe): |
| 42 raise NotImplementedError() | 46 raise NotImplementedError() |
| 43 | 47 |
| 44 @property | 48 @property |
| 45 def local_name(self): | 49 def local_name(self): |
| 46 raise NotImplementedError() | 50 raise NotImplementedError() |
| 47 | 51 |
| 48 @property | 52 @property |
| 49 def unique_name(self): | 53 def unique_name(self): |
| 50 """A unique identifier for the module that this dependency refers to. | 54 """A unique identifier for the module that this dependency refers to. |
| 51 This must be generated without loading the module.""" | 55 This must be generated without loading the module.""" |
| 52 raise NotImplementedError() | 56 raise NotImplementedError() |
| 53 | 57 |
| 54 | 58 |
| 55 class PathDependency(Dependency): | 59 class PathDependency(Dependency): |
| 56 def __init__(self, path, local_name, base_path=None): | 60 def __init__(self, path, local_name, universe, base_path=None): |
| 57 self._path = _normalize_path(base_path, path) | 61 self._path = _normalize_path(base_path, path) |
| 58 self._local_name = local_name | 62 self._local_name = local_name |
| 59 | 63 |
| 60 # We forbid modules from living outside our main paths to keep clients | 64 # We forbid modules from living outside our main paths to keep clients |
| 61 # from going crazy before we have standardized recipe locations. | 65 # from going crazy before we have standardized recipe locations. |
| 62 mod_dir = os.path.dirname(path) | 66 mod_dir = os.path.dirname(path) |
| 63 assert mod_dir in MODULE_DIRS(), ( | 67 assert mod_dir in universe.module_dirs, ( |
| 64 'Modules living outside of approved directories are forbidden: ' | 68 'Modules living outside of approved directories are forbidden: ' |
| 65 '%s is not in %s' % (mod_dir, MODULE_DIRS())) | 69 '%s is not in %s' % (mod_dir, universe.module_dirs)) |
| 66 | 70 |
| 67 def load(self, universe): | 71 def load(self, universe): |
| 68 return _load_recipe_module_module(self._path, universe) | 72 return _load_recipe_module_module(self._path, universe) |
| 69 | 73 |
| 70 @property | 74 @property |
| 71 def local_name(self): | 75 def local_name(self): |
| 72 return self._local_name | 76 return self._local_name |
| 73 | 77 |
| 74 @property | 78 @property |
| 75 def unique_name(self): | 79 def unique_name(self): |
| 76 return self._path | 80 return self._path |
| 77 | 81 |
| 78 | 82 |
| 79 class NamedDependency(PathDependency): | 83 class NamedDependency(PathDependency): |
| 80 def __init__(self, name): | 84 def __init__(self, name, universe): |
| 81 for path in MODULE_DIRS(): | 85 for path in universe.module_dirs: |
| 82 mod_path = os.path.join(path, name) | 86 mod_path = os.path.join(path, name) |
| 83 if os.path.exists(os.path.join(mod_path, '__init__.py')): | 87 if os.path.exists(os.path.join(mod_path, '__init__.py')): |
| 84 super(NamedDependency, self).__init__(mod_path, name) | 88 super(NamedDependency, self).__init__(mod_path, name, universe=universe) |
| 85 return | 89 return |
| 86 raise NoSuchRecipe('Recipe module named %s does not exist' % name) | 90 raise NoSuchRecipe('Recipe module named %s does not exist' % name) |
| 87 | 91 |
| 88 | 92 |
| 89 class RecipeUniverse(object): | 93 class RecipeUniverse(object): |
| 90 def __init__(self): | 94 def __init__(self, module_dirs, recipe_dirs): |
| 91 self._loaded = {} | 95 self._loaded = {} |
| 96 self._module_dirs = module_dirs[:] |
| 97 self._recipe_dirs = recipe_dirs[:] |
| 98 |
| 99 @property |
| 100 def module_dirs(self): |
| 101 return self._module_dirs |
| 102 |
| 103 @property |
| 104 def recipe_dirs(self): |
| 105 return self._recipe_dirs |
| 92 | 106 |
| 93 def load(self, dep): | 107 def load(self, dep): |
| 94 """Load a Dependency.""" | 108 """Load a Dependency.""" |
| 95 name = dep.unique_name | 109 name = dep.unique_name |
| 96 if name in self._loaded: | 110 if name in self._loaded: |
| 97 mod = self._loaded[name] | 111 mod = self._loaded[name] |
| 98 assert mod is not None, ( | 112 assert mod is not None, ( |
| 99 'Cyclic dependency when trying to load %s' % name) | 113 'Cyclic dependency when trying to load %s' % name) |
| 100 return mod | 114 return mod |
| 101 else: | 115 else: |
| 102 self._loaded[name] = None | 116 self._loaded[name] = None |
| 103 mod = dep.load(self) | 117 mod = dep.load(self) |
| 104 self._loaded[name] = mod | 118 self._loaded[name] = mod |
| 105 return mod | 119 return mod |
| 106 | 120 |
| 107 def deps_from_names(self, deps): | 121 def deps_from_names(self, deps): |
| 108 """Load dependencies given a list simple module names (old style).""" | 122 """Load dependencies given a list simple module names (old style).""" |
| 109 return { dep: self.load(NamedDependency(dep)) for dep in deps } | 123 return { dep: self.load(NamedDependency(dep, universe=self)) |
| 124 for dep in deps } |
| 110 | 125 |
| 111 def deps_from_paths(self, deps, base_path): | 126 def deps_from_paths(self, deps, base_path): |
| 112 """Load dependencies given a dictionary of local names to module paths | 127 """Load dependencies given a dictionary of local names to module paths |
| 113 (new style).""" | 128 (new style).""" |
| 114 return { name: self.load(PathDependency(path, name, base_path)) | 129 return { name: self.load(PathDependency(path, name, |
| 130 universe=self, base_path=base_path)) |
| 115 for name, path in deps.iteritems() } | 131 for name, path in deps.iteritems() } |
| 116 | 132 |
| 117 def deps_from_mixed(self, deps, base_path): | 133 def deps_from_mixed(self, deps, base_path): |
| 118 """Load dependencies given either a new style or old style deps spec.""" | 134 """Load dependencies given either a new style or old style deps spec.""" |
| 119 if isinstance(deps, (list, tuple)): | 135 if isinstance(deps, (list, tuple)): |
| 120 return self.deps_from_names(deps) | 136 return self.deps_from_names(deps) |
| 121 elif isinstance(deps, dict): | 137 elif isinstance(deps, dict): |
| 122 return self.deps_from_paths(deps, base_path) | 138 return self.deps_from_paths(deps, base_path) |
| 123 else: | 139 else: |
| 124 raise ValueError('%s is not a valid or known deps structure' % deps) | 140 raise ValueError('%s is not a valid or known deps structure' % deps) |
| 125 | 141 |
| 126 def load_recipe(self, recipe): | 142 def load_recipe(self, recipe): |
| 127 """Given name of a recipe, loads and returns it as RecipeScript instance. | 143 """Given name of a recipe, loads and returns it as RecipeScript instance. |
| 128 | 144 |
| 129 Args: | 145 Args: |
| 130 recipe (str): name of a recipe, can be in form '<module>:<recipe>'. | 146 recipe (str): name of a recipe, can be in form '<module>:<recipe>'. |
| 131 | 147 |
| 132 Returns: | 148 Returns: |
| 133 RecipeScript instance. | 149 RecipeScript instance. |
| 134 | 150 |
| 135 Raises: | 151 Raises: |
| 136 NoSuchRecipe: recipe is not found. | 152 NoSuchRecipe: recipe is not found. |
| 137 """ | 153 """ |
| 138 # If the recipe is specified as "module:recipe", then it is an recipe | 154 # If the recipe is specified as "module:recipe", then it is an recipe |
| 139 # contained in a recipe_module as an example. Look for it in the modules | 155 # contained in a recipe_module as an example. Look for it in the modules |
| 140 # imported by load_recipe_modules instead of the normal search paths. | 156 # imported by load_recipe_modules instead of the normal search paths. |
| 141 if ':' in recipe: | 157 if ':' in recipe: |
| 142 module_name, example = recipe.split(':') | 158 module_name, example = recipe.split(':') |
| 143 assert example.endswith('example') | 159 assert example.endswith('example') |
| 144 for module_dir in MODULE_DIRS(): | 160 for module_dir in self.module_dirs: |
| 145 for subitem in os.listdir(module_dir): | 161 for subitem in os.listdir(module_dir): |
| 146 if module_name == subitem: | 162 if module_name == subitem: |
| 147 return RecipeScript.from_script_path( | 163 return RecipeScript.from_script_path( |
| 148 os.path.join(module_dir, subitem, 'example.py'), self) | 164 os.path.join(module_dir, subitem, 'example.py'), self) |
| 149 raise NoSuchRecipe(recipe, | 165 raise NoSuchRecipe(recipe, |
| 150 'Recipe example %s:%s does not exist' % | 166 'Recipe example %s:%s does not exist' % |
| 151 (module_name, example)) | 167 (module_name, example)) |
| 152 else: | 168 else: |
| 153 for recipe_path in (os.path.join(p, recipe) for p in RECIPE_DIRS()): | 169 for recipe_path in (os.path.join(p, recipe) for p in self.recipe_dirs): |
| 154 if os.path.exists(recipe_path + '.py'): | 170 if os.path.exists(recipe_path + '.py'): |
| 155 return RecipeScript.from_script_path(recipe_path + '.py', self) | 171 return RecipeScript.from_script_path(recipe_path + '.py', self) |
| 156 raise NoSuchRecipe(recipe) | 172 raise NoSuchRecipe(recipe) |
| 157 | 173 |
| 174 def loop_over_recipe_modules(self): |
| 175 for path in self.module_dirs: |
| 176 if os.path.isdir(path): |
| 177 for item in os.listdir(path): |
| 178 subpath = os.path.join(path, item) |
| 179 if os.path.isdir(subpath): |
| 180 yield subpath |
| 158 | 181 |
| 182 def loop_over_recipes(self): |
| 183 """Yields pairs (path to recipe, recipe name). |
| 184 |
| 185 Enumerates real recipes in recipes/* as well as examples in recipe_modules/*
. |
| 186 """ |
| 187 for path in self.recipe_dirs: |
| 188 for recipe in scan_directory( |
| 189 path, lambda f: f.endswith('.py') and f[0] != '_'): |
| 190 yield recipe, recipe[len(path)+1:-len('.py')] |
| 191 for path in self.module_dirs: |
| 192 for recipe in scan_directory( |
| 193 path, lambda f: f.endswith('example.py')): |
| 194 module_name = os.path.dirname(recipe)[len(path)+1:] |
| 195 yield recipe, '%s:example' % module_name |
| 196 |
| 197 |
| 198 @contextlib.contextmanager |
| 199 def _preserve_path(): |
| 200 old_path = sys.path[:] |
| 201 try: |
| 202 yield |
| 203 finally: |
| 204 sys.path = old_path |
| 205 |
| 206 |
| 207 def _normalize_path(base_path, path): |
| 208 if base_path is None or os.path.isabs(path): |
| 209 return os.path.realpath(path) |
| 210 else: |
| 211 return os.path.realpath(os.path.join(base_path, path)) |
| 159 | 212 |
| 160 | 213 |
| 161 def _find_and_load_module(fullname, modname, path): | 214 def _find_and_load_module(fullname, modname, path): |
| 162 imp.acquire_lock() | 215 imp.acquire_lock() |
| 163 try: | 216 try: |
| 164 if fullname not in sys.modules: | 217 if fullname not in sys.modules: |
| 165 fil = None | 218 fil = None |
| 166 try: | 219 try: |
| 167 fil, pathname, descr = imp.find_module(modname, | 220 fil, pathname, descr = imp.find_module(modname, |
| 168 [os.path.dirname(path)]) | 221 [os.path.dirname(path)]) |
| 169 imp.load_module(fullname, fil, pathname, descr) | 222 imp.load_module(fullname, fil, pathname, descr) |
| 170 finally: | 223 finally: |
| 171 if fil: | 224 if fil: |
| 172 fil.close() | 225 fil.close() |
| 173 return sys.modules[fullname] | 226 return sys.modules[fullname] |
| 174 finally: | 227 finally: |
| 175 imp.release_lock() | 228 imp.release_lock() |
| 176 | 229 |
| 177 | 230 |
| 178 def _load_recipe_module_module(path, universe): | 231 def _load_recipe_module_module(path, universe): |
| 179 modname = os.path.splitext(os.path.basename(path))[0] | 232 modname = os.path.splitext(os.path.basename(path))[0] |
| 180 fullname = '%s.%s' % (RECIPE_MODULE_PREFIX, modname) | 233 fullname = '%s.%s' % (RECIPE_MODULE_PREFIX, modname) |
| 181 mod = _find_and_load_module(fullname, modname, path) | 234 mod = _find_and_load_module(fullname, modname, path) |
| 182 | 235 |
| 183 # This actually loads the dependencies. | 236 # This actually loads the dependencies. |
| 184 mod.LOADED_DEPS = universe.deps_from_mixed( | 237 mod.LOADED_DEPS = universe.deps_from_mixed( |
| 185 getattr(mod, 'DEPS', []), os.path.basename(path)) | 238 getattr(mod, 'DEPS', []), os.path.basename(path)) |
| 186 | 239 |
| 187 # TODO(luqui): Remove this hack once configs are cleaned. | 240 # Prevent any modules that mess with sys.path from leaking. |
| 188 sys.modules['%s.DEPS' % fullname] = mod.LOADED_DEPS | 241 with _preserve_path(): |
| 189 _recursive_import(path, RECIPE_MODULE_PREFIX) | 242 # TODO(luqui): Remove this hack once configs are cleaned. |
| 190 _patchup_module(modname, mod) | 243 sys.modules['%s.DEPS' % fullname] = mod.LOADED_DEPS |
| 244 _recursive_import(path, RECIPE_MODULE_PREFIX) |
| 245 _patchup_module(modname, mod) |
| 191 | 246 |
| 192 return mod | 247 return mod |
| 193 | 248 |
| 194 | 249 |
| 195 def _recursive_import(path, prefix): | 250 def _recursive_import(path, prefix): |
| 196 modname = os.path.splitext(os.path.basename(path))[0] | 251 modname = os.path.splitext(os.path.basename(path))[0] |
| 197 fullname = '%s.%s' % (prefix, modname) | 252 fullname = '%s.%s' % (prefix, modname) |
| 198 mod = _find_and_load_module(fullname, modname, path) | 253 mod = _find_and_load_module(fullname, modname, path) |
| 199 if not os.path.isdir(path): | 254 if not os.path.isdir(path): |
| 200 return mod | 255 return mod |
| (...skipping 127 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 328 setattr(modapi.m, k, v) | 383 setattr(modapi.m, k, v) |
| 329 return modapi | 384 return modapi |
| 330 | 385 |
| 331 mapper = DependencyMapper(instantiator) | 386 mapper = DependencyMapper(instantiator) |
| 332 api = RecipeTestApi(module=None) | 387 api = RecipeTestApi(module=None) |
| 333 for k,v in toplevel_deps.iteritems(): | 388 for k,v in toplevel_deps.iteritems(): |
| 334 setattr(api, k, mapper.instantiate(v)) | 389 setattr(api, k, mapper.instantiate(v)) |
| 335 return api | 390 return api |
| 336 | 391 |
| 337 | 392 |
| 338 def loop_over_recipe_modules(): | |
| 339 for path in MODULE_DIRS(): | |
| 340 if os.path.isdir(path): | |
| 341 for item in os.listdir(path): | |
| 342 subpath = os.path.join(path, item) | |
| 343 if os.path.isdir(subpath): | |
| 344 yield subpath | |
| 345 | 393 |
| 346 | |
| 347 def loop_over_recipes(): | |
| 348 """Yields pairs (path to recipe, recipe name). | |
| 349 | |
| 350 Enumerates real recipes in recipes/* as well as examples in recipe_modules/*. | |
| 351 """ | |
| 352 for path in RECIPE_DIRS(): | |
| 353 for recipe in scan_directory( | |
| 354 path, lambda f: f.endswith('.py') and f[0] != '_'): | |
| 355 yield recipe, recipe[len(path)+1:-len('.py')] | |
| 356 for path in MODULE_DIRS(): | |
| 357 for recipe in scan_directory( | |
| 358 path, lambda f: f.endswith('example.py')): | |
| 359 module_name = os.path.dirname(recipe)[len(path)+1:] | |
| 360 yield recipe, '%s:example' % module_name | |
| 361 | |
| 362 | |
| 363 def _normalize_path(base_path, path): | |
| 364 if base_path is None or os.path.isabs(path): | |
| 365 return os.path.realpath(path) | |
| 366 else: | |
| 367 return os.path.realpath(os.path.join(base_path, path)) | |
| 368 | |
| OLD | NEW |