| OLD | NEW |
| 1 # Copyright 2013-2015 The Chromium Authors. All rights reserved. | 1 # Copyright 2013-2015 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 contextlib |
| 6 import imp | 6 import imp |
| 7 import inspect | 7 import inspect |
| 8 import os | 8 import os |
| 9 import sys | 9 import sys |
| 10 | 10 |
| (...skipping 23 matching lines...) Expand all Loading... |
| 34 @classmethod | 34 @classmethod |
| 35 def from_script_path(cls, script_path, universe): | 35 def from_script_path(cls, script_path, universe): |
| 36 """Evaluates a script and returns RecipeScript instance.""" | 36 """Evaluates a script and returns RecipeScript instance.""" |
| 37 | 37 |
| 38 script_vars = {} | 38 script_vars = {} |
| 39 script_vars['__file__'] = script_path | 39 script_vars['__file__'] = script_path |
| 40 | 40 |
| 41 with _preserve_path(): | 41 with _preserve_path(): |
| 42 execfile(script_path, script_vars) | 42 execfile(script_path, script_vars) |
| 43 | 43 |
| 44 script_vars['LOADED_DEPS'] = universe.deps_from_mixed( | 44 script_vars['LOADED_DEPS'] = universe.deps_from_spec( |
| 45 script_vars.get('DEPS', []), os.path.basename(script_path)) | 45 script_vars.get('DEPS', [])) |
| 46 return cls(script_vars) | 46 return cls(script_vars) |
| 47 | 47 |
| 48 | 48 |
| 49 class Dependency(object): | 49 class Dependency(object): |
| 50 def load(self, universe): | 50 def load(self, universe): |
| 51 raise NotImplementedError() | 51 raise NotImplementedError() |
| 52 | 52 |
| 53 @property | 53 @property |
| 54 def local_name(self): | 54 def local_name(self): |
| 55 raise NotImplementedError() | 55 raise NotImplementedError() |
| 56 | 56 |
| 57 @property | 57 @property |
| 58 def unique_name(self): | 58 def unique_name(self): |
| 59 """A unique identifier for the module that this dependency refers to. | 59 """A unique identifier for the module that this dependency refers to. |
| 60 This must be generated without loading the module.""" | 60 This must be generated without loading the module.""" |
| 61 raise NotImplementedError() | 61 raise NotImplementedError() |
| 62 | 62 |
| 63 | 63 |
| 64 class PathDependency(Dependency): | 64 class PathDependency(Dependency): |
| 65 def __init__(self, path, local_name, universe, base_path=None): | 65 def __init__(self, path, local_name, universe): |
| 66 self._path = _normalize_path(base_path, path) | 66 assert os.path.isabs(path), ( |
| 67 'Path dependencies must be absolute, but %s is not' % path) |
| 68 self._path = path |
| 67 self._local_name = local_name | 69 self._local_name = local_name |
| 68 | 70 |
| 69 # We forbid modules from living outside our main paths to keep clients | 71 # We forbid modules from living outside our main paths to keep clients |
| 70 # from going crazy before we have standardized recipe locations. | 72 # from going crazy before we have standardized recipe locations. |
| 71 mod_dir = os.path.dirname(path) | 73 mod_dir = os.path.dirname(path) |
| 72 assert mod_dir in universe.module_dirs, ( | 74 assert mod_dir in universe.module_dirs, ( |
| 73 'Modules living outside of approved directories are forbidden: ' | 75 'Modules living outside of approved directories are forbidden: ' |
| 74 '%s is not in %s' % (mod_dir, universe.module_dirs)) | 76 '%s is not in %s' % (mod_dir, universe.module_dirs)) |
| 75 | 77 |
| 76 def load(self, universe): | 78 def load(self, universe): |
| (...skipping 11 matching lines...) Expand all Loading... |
| 88 class NamedDependency(PathDependency): | 90 class NamedDependency(PathDependency): |
| 89 def __init__(self, name, universe): | 91 def __init__(self, name, universe): |
| 90 for path in universe.module_dirs: | 92 for path in universe.module_dirs: |
| 91 mod_path = os.path.join(path, name) | 93 mod_path = os.path.join(path, name) |
| 92 if _is_recipe_module_dir(mod_path): | 94 if _is_recipe_module_dir(mod_path): |
| 93 super(NamedDependency, self).__init__(mod_path, name, universe=universe) | 95 super(NamedDependency, self).__init__(mod_path, name, universe=universe) |
| 94 return | 96 return |
| 95 raise NoSuchRecipe('Recipe module named %s does not exist' % name) | 97 raise NoSuchRecipe('Recipe module named %s does not exist' % name) |
| 96 | 98 |
| 97 | 99 |
| 100 class PackageDependency(PathDependency): |
| 101 # TODO(luqui): Forbid depending on a module from a (locally) undeclared |
| 102 # dependency. |
| 103 def __init__(self, package, module, local_name, universe): |
| 104 mod_path = ( |
| 105 universe.package_deps.get_package(package).module_path(module)) |
| 106 super(PackageDependency, self).__init__( |
| 107 mod_path, local_name, universe=universe) |
| 108 |
| 109 |
| 98 class RecipeUniverse(object): | 110 class RecipeUniverse(object): |
| 99 def __init__(self, module_dirs, recipe_dirs): | 111 def __init__(self, package_deps): |
| 100 self._loaded = {} | 112 self._loaded = {} |
| 101 self._module_dirs = module_dirs[:] | 113 self._package_deps = package_deps |
| 102 self._recipe_dirs = recipe_dirs[:] | |
| 103 | 114 |
| 104 @property | 115 @property |
| 105 def module_dirs(self): | 116 def module_dirs(self): |
| 106 return self._module_dirs | 117 return self._package_deps.all_module_dirs |
| 107 | 118 |
| 108 @property | 119 @property |
| 109 def recipe_dirs(self): | 120 def recipe_dirs(self): |
| 110 return self._recipe_dirs | 121 return self._package_deps.all_recipe_dirs |
| 122 |
| 123 @property |
| 124 def package_deps(self): |
| 125 return self._package_deps |
| 111 | 126 |
| 112 def load(self, dep): | 127 def load(self, dep): |
| 113 """Load a Dependency.""" | 128 """Load a Dependency.""" |
| 114 name = dep.unique_name | 129 name = dep.unique_name |
| 115 if name in self._loaded: | 130 if name in self._loaded: |
| 116 mod = self._loaded[name] | 131 mod = self._loaded[name] |
| 117 assert mod is not None, ( | 132 assert mod is not None, ( |
| 118 'Cyclic dependency when trying to load %s' % name) | 133 'Cyclic dependency when trying to load %s' % name) |
| 119 return mod | 134 return mod |
| 120 else: | 135 else: |
| 121 self._loaded[name] = None | 136 self._loaded[name] = None |
| 122 mod = dep.load(self) | 137 mod = dep.load(self) |
| 123 self._loaded[name] = mod | 138 self._loaded[name] = mod |
| 124 return mod | 139 return mod |
| 125 | 140 |
| 126 def deps_from_names(self, deps): | 141 def _dep_from_name(self, name): |
| 127 """Load dependencies given a list simple module names (old style).""" | 142 if '/' in name: |
| 128 return { dep: self.load(NamedDependency(dep, universe=self)) | 143 [package,module] = name.split('/') |
| 129 for dep in deps } | 144 dep = PackageDependency(package, module, module, universe=self) |
| 145 else: |
| 146 # Old style: bare module name, search paths to find it. |
| 147 module = name |
| 148 dep = NamedDependency(name, universe=self) |
| 130 | 149 |
| 131 def deps_from_paths(self, deps, base_path): | 150 return module, dep |
| 132 """Load dependencies given a dictionary of local names to module paths | |
| 133 (new style).""" | |
| 134 return { name: self.load(PathDependency(path, name, | |
| 135 universe=self, base_path=base_path)) | |
| 136 for name, path in deps.iteritems() } | |
| 137 | 151 |
| 138 def deps_from_mixed(self, deps, base_path): | 152 def deps_from_spec(self, spec): |
| 139 """Load dependencies given either a new style or old style deps spec.""" | 153 # Automatic local names. |
| 140 if isinstance(deps, (list, tuple)): | 154 if isinstance(spec, (list, tuple)): |
| 141 return self.deps_from_names(deps) | 155 deps = {} |
| 142 elif isinstance(deps, dict): | 156 for item in spec: |
| 143 return self.deps_from_paths(deps, base_path) | 157 name, dep = self._dep_from_name(item) |
| 144 else: | 158 deps[name] = self.load(dep) |
| 145 raise ValueError('%s is not a valid or known deps structure' % deps) | 159 # Explicit local names. |
| 160 elif isinstance(spec, dict): |
| 161 deps = {} |
| 162 for name, item in spec.iteritems(): |
| 163 _, dep = self._dep_from_name(item) |
| 164 deps[name] = self.load(dep) |
| 165 return deps |
| 146 | 166 |
| 147 def load_recipe(self, recipe): | 167 def load_recipe(self, recipe): |
| 148 """Given name of a recipe, loads and returns it as RecipeScript instance. | 168 """Given name of a recipe, loads and returns it as RecipeScript instance. |
| 149 | 169 |
| 150 Args: | 170 Args: |
| 151 recipe (str): name of a recipe, can be in form '<module>:<recipe>'. | 171 recipe (str): name of a recipe, can be in form '<module>:<recipe>'. |
| 152 | 172 |
| 153 Returns: | 173 Returns: |
| 154 RecipeScript instance. | 174 RecipeScript instance. |
| 155 | 175 |
| 156 Raises: | 176 Raises: |
| 157 NoSuchRecipe: recipe is not found. | 177 NoSuchRecipe: recipe is not found. |
| 158 """ | 178 """ |
| 159 # If the recipe is specified as "module:recipe", then it is an recipe | 179 # If the recipe is specified as "module:recipe", then it is an recipe |
| 160 # contained in a recipe_module as an example. Look for it in the modules | 180 # contained in a recipe_module as an example. Look for it in the modules |
| 161 # imported by load_recipe_modules instead of the normal search paths. | 181 # imported by load_recipe_modules instead of the normal search paths. |
| 162 if ':' in recipe: | 182 if ':' in recipe: |
| 163 module_name, example = recipe.split(':') | 183 module_name, example = recipe.split(':') |
| 164 assert example.endswith('example') | 184 assert example.endswith('example') |
| 165 for module_dir in self.module_dirs: | 185 for module_dir in self.module_dirs: |
| 166 for subitem in os.listdir(module_dir): | 186 if os.path.isdir(module_dir): |
| 167 if module_name == subitem: | 187 for subitem in os.listdir(module_dir): |
| 168 return RecipeScript.from_script_path( | 188 if module_name == subitem: |
| 169 os.path.join(module_dir, subitem, 'example.py'), self) | 189 return RecipeScript.from_script_path( |
| 190 os.path.join(module_dir, subitem, 'example.py'), self) |
| 170 raise NoSuchRecipe(recipe, | 191 raise NoSuchRecipe(recipe, |
| 171 'Recipe example %s:%s does not exist' % | 192 'Recipe example %s:%s does not exist' % |
| 172 (module_name, example)) | 193 (module_name, example)) |
| 173 else: | 194 else: |
| 174 for recipe_path in (os.path.join(p, recipe) for p in self.recipe_dirs): | 195 for recipe_path in (os.path.join(p, recipe) for p in self.recipe_dirs): |
| 175 if os.path.exists(recipe_path + '.py'): | 196 if os.path.exists(recipe_path + '.py'): |
| 176 return RecipeScript.from_script_path(recipe_path + '.py', self) | 197 return RecipeScript.from_script_path(recipe_path + '.py', self) |
| 177 raise NoSuchRecipe(recipe) | 198 raise NoSuchRecipe(recipe) |
| 178 | 199 |
| 179 def loop_over_recipe_modules(self): | 200 def loop_over_recipe_modules(self): |
| (...skipping 27 matching lines...) Expand all Loading... |
| 207 | 228 |
| 208 @contextlib.contextmanager | 229 @contextlib.contextmanager |
| 209 def _preserve_path(): | 230 def _preserve_path(): |
| 210 old_path = sys.path[:] | 231 old_path = sys.path[:] |
| 211 try: | 232 try: |
| 212 yield | 233 yield |
| 213 finally: | 234 finally: |
| 214 sys.path = old_path | 235 sys.path = old_path |
| 215 | 236 |
| 216 | 237 |
| 217 def _normalize_path(base_path, path): | |
| 218 if base_path is None or os.path.isabs(path): | |
| 219 return os.path.realpath(path) | |
| 220 else: | |
| 221 return os.path.realpath(os.path.join(base_path, path)) | |
| 222 | |
| 223 | |
| 224 def _find_and_load_module(fullname, modname, path): | 238 def _find_and_load_module(fullname, modname, path): |
| 225 imp.acquire_lock() | 239 imp.acquire_lock() |
| 226 try: | 240 try: |
| 227 if fullname not in sys.modules: | 241 if fullname not in sys.modules: |
| 228 fil = None | 242 fil = None |
| 229 try: | 243 try: |
| 230 fil, pathname, descr = imp.find_module(modname, | 244 fil, pathname, descr = imp.find_module(modname, |
| 231 [os.path.dirname(path)]) | 245 [os.path.dirname(path)]) |
| 232 imp.load_module(fullname, fil, pathname, descr) | 246 imp.load_module(fullname, fil, pathname, descr) |
| 233 finally: | 247 finally: |
| 234 if fil: | 248 if fil: |
| 235 fil.close() | 249 fil.close() |
| 236 return sys.modules[fullname] | 250 return sys.modules[fullname] |
| 237 finally: | 251 finally: |
| 238 imp.release_lock() | 252 imp.release_lock() |
| 239 | 253 |
| 240 | 254 |
| 241 def _load_recipe_module_module(path, universe): | 255 def _load_recipe_module_module(path, universe): |
| 242 modname = os.path.splitext(os.path.basename(path))[0] | 256 modname = os.path.splitext(os.path.basename(path))[0] |
| 243 fullname = '%s.%s' % (RECIPE_MODULE_PREFIX, modname) | 257 fullname = '%s.%s' % (RECIPE_MODULE_PREFIX, modname) |
| 244 mod = _find_and_load_module(fullname, modname, path) | 258 mod = _find_and_load_module(fullname, modname, path) |
| 245 | 259 |
| 246 # This actually loads the dependencies. | 260 # This actually loads the dependencies. |
| 247 mod.LOADED_DEPS = universe.deps_from_mixed( | 261 mod.LOADED_DEPS = universe.deps_from_spec(getattr(mod, 'DEPS', [])) |
| 248 getattr(mod, 'DEPS', []), os.path.basename(path)) | |
| 249 | 262 |
| 250 # Prevent any modules that mess with sys.path from leaking. | 263 # Prevent any modules that mess with sys.path from leaking. |
| 251 with _preserve_path(): | 264 with _preserve_path(): |
| 252 # TODO(luqui): Remove this hack once configs are cleaned. | 265 # TODO(luqui): Remove this hack once configs are cleaned. |
| 253 sys.modules['%s.DEPS' % fullname] = mod.LOADED_DEPS | 266 sys.modules['%s.DEPS' % fullname] = mod.LOADED_DEPS |
| 254 _recursive_import(path, RECIPE_MODULE_PREFIX) | 267 _recursive_import(path, RECIPE_MODULE_PREFIX) |
| 255 _patchup_module(modname, mod) | 268 _patchup_module(modname, mod) |
| 256 | 269 |
| 257 return mod | 270 return mod |
| 258 | 271 |
| (...skipping 192 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 451 return modapi | 464 return modapi |
| 452 | 465 |
| 453 mapper = DependencyMapper(instantiator) | 466 mapper = DependencyMapper(instantiator) |
| 454 api = RecipeTestApi(module=None) | 467 api = RecipeTestApi(module=None) |
| 455 for k,v in toplevel_deps.iteritems(): | 468 for k,v in toplevel_deps.iteritems(): |
| 456 setattr(api, k, mapper.instantiate(v)) | 469 setattr(api, k, mapper.instantiate(v)) |
| 457 return api | 470 return api |
| 458 | 471 |
| 459 | 472 |
| 460 | 473 |
| OLD | NEW |