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 |