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