Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1431)

Side by Side Diff: recipe_engine/loader.py

Issue 1344583003: Recipe package system. (Closed) Base URL: git@github.com:luci/recipes-py.git@master
Patch Set: Recompiled proto Created 5 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « recipe_engine/lint_test.py ('k') | recipe_engine/package.proto » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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
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
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
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
OLDNEW
« no previous file with comments | « recipe_engine/lint_test.py ('k') | recipe_engine/package.proto » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698