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 _is_recipe_module_dir(mod_path): | 89 if _is_recipe_module_dir(mod_path): |
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 27 matching lines...) Expand all Loading... |
202 | 223 |
203 @contextlib.contextmanager | 224 @contextlib.contextmanager |
204 def _preserve_path(): | 225 def _preserve_path(): |
205 old_path = sys.path[:] | 226 old_path = sys.path[:] |
206 try: | 227 try: |
207 yield | 228 yield |
208 finally: | 229 finally: |
209 sys.path = old_path | 230 sys.path = old_path |
210 | 231 |
211 | 232 |
212 def _normalize_path(base_path, path): | |
213 if base_path is None or os.path.isabs(path): | |
214 return os.path.realpath(path) | |
215 else: | |
216 return os.path.realpath(os.path.join(base_path, path)) | |
217 | |
218 | |
219 def _find_and_load_module(fullname, modname, path): | 233 def _find_and_load_module(fullname, modname, path): |
220 imp.acquire_lock() | 234 imp.acquire_lock() |
221 try: | 235 try: |
222 if fullname not in sys.modules: | 236 if fullname not in sys.modules: |
223 fil = None | 237 fil = None |
224 try: | 238 try: |
225 fil, pathname, descr = imp.find_module(modname, | 239 fil, pathname, descr = imp.find_module(modname, |
226 [os.path.dirname(path)]) | 240 [os.path.dirname(path)]) |
227 imp.load_module(fullname, fil, pathname, descr) | 241 imp.load_module(fullname, fil, pathname, descr) |
228 finally: | 242 finally: |
229 if fil: | 243 if fil: |
230 fil.close() | 244 fil.close() |
231 return sys.modules[fullname] | 245 return sys.modules[fullname] |
232 finally: | 246 finally: |
233 imp.release_lock() | 247 imp.release_lock() |
234 | 248 |
235 | 249 |
236 def _load_recipe_module_module(path, universe): | 250 def _load_recipe_module_module(path, universe): |
237 modname = os.path.splitext(os.path.basename(path))[0] | 251 modname = os.path.splitext(os.path.basename(path))[0] |
238 fullname = '%s.%s' % (RECIPE_MODULE_PREFIX, modname) | 252 fullname = '%s.%s' % (RECIPE_MODULE_PREFIX, modname) |
239 mod = _find_and_load_module(fullname, modname, path) | 253 mod = _find_and_load_module(fullname, modname, path) |
240 | 254 |
241 # This actually loads the dependencies. | 255 # This actually loads the dependencies. |
242 mod.LOADED_DEPS = universe.deps_from_mixed( | 256 mod.LOADED_DEPS = universe.deps_from_spec(getattr(mod, 'DEPS', [])) |
243 getattr(mod, 'DEPS', []), os.path.basename(path)) | |
244 | 257 |
245 # Prevent any modules that mess with sys.path from leaking. | 258 # Prevent any modules that mess with sys.path from leaking. |
246 with _preserve_path(): | 259 with _preserve_path(): |
247 # TODO(luqui): Remove this hack once configs are cleaned. | 260 # TODO(luqui): Remove this hack once configs are cleaned. |
248 sys.modules['%s.DEPS' % fullname] = mod.LOADED_DEPS | 261 sys.modules['%s.DEPS' % fullname] = mod.LOADED_DEPS |
249 _recursive_import(path, RECIPE_MODULE_PREFIX) | 262 _recursive_import(path, RECIPE_MODULE_PREFIX) |
250 _patchup_module(modname, mod) | 263 _patchup_module(modname, mod) |
251 | 264 |
252 return mod | 265 return mod |
253 | 266 |
(...skipping 135 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
389 return modapi | 402 return modapi |
390 | 403 |
391 mapper = DependencyMapper(instantiator) | 404 mapper = DependencyMapper(instantiator) |
392 api = RecipeTestApi(module=None) | 405 api = RecipeTestApi(module=None) |
393 for k,v in toplevel_deps.iteritems(): | 406 for k,v in toplevel_deps.iteritems(): |
394 setattr(api, k, mapper.instantiate(v)) | 407 setattr(api, k, mapper.instantiate(v)) |
395 return api | 408 return api |
396 | 409 |
397 | 410 |
398 | 411 |
OLD | NEW |