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

Side by Side Diff: scripts/slave/recipe_loader.py

Issue 187203005: Minor cleanup of some recipe framework code. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/build
Patch Set: Created 6 years, 9 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 | Annotate | Revision Log
OLDNEW
1 # Copyright 2013 The Chromium Authors. All rights reserved. 1 # Copyright 2013 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 import copy 4 import copy
5 import imp 5 import imp
6 import inspect 6 import inspect
7 import os 7 import os
8 import sys 8 import sys
9 from functools import wraps 9 from functools import wraps
10 10
11 from .recipe_util import RECIPE_DIRS, MODULE_DIRS 11 from .recipe_util import RECIPE_DIRS, MODULE_DIRS
12 from .recipe_api import RecipeApi 12 from .recipe_api import RecipeApi
13 from .recipe_config import ConfigContext
13 from .recipe_test_api import RecipeTestApi, DisabledTestData, ModuleTestData 14 from .recipe_test_api import RecipeTestApi, DisabledTestData, ModuleTestData
14 15
15 16
16 def load_recipe_modules(mod_dirs): 17 def load_recipe_modules(mod_dirs):
17 def patchup_module(name, submod): 18 def patchup_module(name, submod):
19 """Finds framework related classes and functions in a |submod| and adds
Vadim Sh. 2014/03/05 06:09:30 That was unclear for me from the first glance.
iannucci 2014/03/05 20:43:37 Thanks. This docstring lg.
20 them to |submod| as top level constants with well known names such as
21 API, CONFIG_CTX and TEST_API.
22
23 |submod| is a recipe module (akin to python package) with submodules such as
iannucci 2014/03/05 20:43:37 We should find a standard way to document types. S
Vadim Sh. 2014/03/05 21:40:38 With current level of magicallity doc browser woul
iannucci 2014/03/05 21:58:30 Maybe. I think it would be more useful than you mi
24 'api', 'config', 'test_api'. This function scans through dicts of that
25 submodules to find subclasses of RecipeApi, RecipeTestApi, etc.
26 """
18 submod.NAME = name 27 submod.NAME = name
19 submod.CONFIG_CTX = getattr(submod, 'CONFIG_CTX', None) 28 submod.CONFIG_CTX = getattr(submod, 'CONFIG_CTX', None)
20 submod.DEPS = frozenset(getattr(submod, 'DEPS', ())) 29 submod.DEPS = frozenset(getattr(submod, 'DEPS', ()))
21 30
22 if hasattr(submod, 'config'): 31 if hasattr(submod, 'config'):
23 for v in submod.config.__dict__.itervalues(): 32 for v in submod.config.__dict__.itervalues():
24 if hasattr(v, 'I_AM_A_CONFIG_CTX'): 33 if isinstance(v, ConfigContext):
Vadim Sh. 2014/03/05 06:09:30 That was WTF moment...
iannucci 2014/03/05 20:43:37 :D You're deep into the Old Code here ^_^. Lookin
25 assert not submod.CONFIG_CTX, ( 34 assert not submod.CONFIG_CTX, (
26 'More than one configuration context: %s' % (submod.config)) 35 'More than one configuration context: %s' % (submod.config))
27 submod.CONFIG_CTX = v 36 submod.CONFIG_CTX = v
28 assert submod.CONFIG_CTX, 'Config file, but no config context?' 37 assert submod.CONFIG_CTX, 'Config file, but no config context?'
29 38
30 submod.API = getattr(submod, 'API', None) 39 submod.API = getattr(submod, 'API', None)
31 for v in submod.api.__dict__.itervalues(): 40 for v in submod.api.__dict__.itervalues():
32 if inspect.isclass(v) and issubclass(v, RecipeApi): 41 if inspect.isclass(v) and issubclass(v, RecipeApi):
33 assert not submod.API, ( 42 assert not submod.API, (
34 'More than one Api subclass: %s' % submod.api) 43 'More than one Api subclass: %s' % submod.api)
(...skipping 73 matching lines...) Expand 10 before | Expand all | Expand 10 after
108 117
109 # Then import all the config extenders. 118 # Then import all the config extenders.
110 for root in mod_dirs: 119 for root in mod_dirs:
111 if os.path.isdir(root): 120 if os.path.isdir(root):
112 recursive_import(root) 121 recursive_import(root)
113 return sys.modules[RM] 122 return sys.modules[RM]
114 finally: 123 finally:
115 imp.release_lock() 124 imp.release_lock()
116 125
117 126
118 def CreateApi(mod_dirs, names, test_data=DisabledTestData(), required=None, 127 def create_api(mod_dirs, names, test_data=DisabledTestData(), required=None,
Vadim Sh. 2014/03/05 06:09:30 I really can't stand mixed code styles in a single
Vadim Sh. 2014/03/05 06:09:30 Also this function is a big WTF for me, I still ha
iannucci 2014/03/05 20:43:37 yep. I think this slipped through a codereview
iannucci 2014/03/05 20:43:37 Basically it creates a RecipeApi instance, with al
119 optional=None, kwargs=None): 128 optional=None, kwargs=None):
120 """ 129 """
121 Given a list of module names, return an instance of RecipeApi which contains 130 Given a list of module names, return an instance of RecipeApi which contains
122 those modules as direct members. 131 those modules as direct members.
123 132
124 So, if you pass ['foobar'], you'll get an instance back which contains a 133 So, if you pass ['foobar'], you'll get an instance back which contains a
125 'foobar' attribute which itself is a RecipeApi instance from the 'foobar' 134 'foobar' attribute which itself is a RecipeApi instance from the 'foobar'
126 module. 135 module.
127 136
128 Args: 137 Args:
138 mod_dirs (list): A list of paths to directories which contain modules.
129 names (list): A list of module names to include in the returned RecipeApi. 139 names (list): A list of module names to include in the returned RecipeApi.
130 mod_dirs (list): A list of paths to directories which contain modules.
131 test_data (TestData): ... 140 test_data (TestData): ...
141 required: a pair such as ('API', RecipeApi).
Vadim Sh. 2014/03/05 06:09:30 I really tried to come up with a better doc string
142 optional: a pair such as ('TEST_API', RecipeTestApi).
132 kwargs: Data passed to each module api. Usually this will contain: 143 kwargs: Data passed to each module api. Usually this will contain:
133 properties (dict): the properties dictionary (used by the properties 144 properties (dict): the properties dictionary (used by the properties
134 module) 145 module)
135 step_history (OrderedDict): the step history object (used by the 146 step_history (OrderedDict): the step history object (used by the
136 step_history module!) 147 step_history module!)
137 """ 148 """
138 kwargs = kwargs or {} 149 kwargs = kwargs or {}
139 recipe_modules = load_recipe_modules(mod_dirs) 150 recipe_modules = load_recipe_modules(mod_dirs)
140 151
141 inst_maps = {} 152 inst_maps = {}
(...skipping 13 matching lines...) Expand all
155 mod_test = DisabledTestData() 166 mod_test = DisabledTestData()
156 if test_data.enabled: 167 if test_data.enabled:
157 mod_test = test_data.mod_data.get(name, ModuleTestData()) 168 mod_test = test_data.mod_data.get(name, ModuleTestData())
158 169
159 if required: 170 if required:
160 api = getattr(module, required[0]) 171 api = getattr(module, required[0])
161 inst_maps[required[0]][name] = api(module=module, 172 inst_maps[required[0]][name] = api(module=module,
162 test_data=mod_test, **kwargs) 173 test_data=mod_test, **kwargs)
163 if optional: 174 if optional:
164 api = getattr(module, optional[0], None) or optional[1] 175 api = getattr(module, optional[0], None) or optional[1]
176 # TODO(vadimsh): Why not pass **kwargs here as well? There's
177 # an assumption here that optional[1] is always RecipeTestApi subclass
178 # (that doesn't need kwargs).
Vadim Sh. 2014/03/05 06:09:30 I dislike this asymmetry...
iannucci 2014/03/05 20:43:37 Yeah me too :(. There shouldn't be kwargs at all.
Vadim Sh. 2014/03/05 21:40:38 It's used by properties and step_history modules.
165 inst_maps[optional[0]][name] = api(module=module, 179 inst_maps[optional[0]][name] = api(module=module,
166 test_data=mod_test) 180 test_data=mod_test)
167 181
168 map(create_maps, names) 182 map(create_maps, names)
169 183
170 if required: 184 if required:
171 MapDependencies(dep_map, inst_maps[required[0]]) 185 map_dependencies(dep_map, inst_maps[required[0]])
172 if optional: 186 if optional:
173 MapDependencies(dep_map, inst_maps[optional[0]]) 187 map_dependencies(dep_map, inst_maps[optional[0]])
174 if required: 188 if required:
175 for name, module in inst_maps[required[0]].iteritems(): 189 for name, module in inst_maps[required[0]].iteritems():
176 module.test_api = inst_maps[optional[0]][name] 190 module.test_api = inst_maps[optional[0]][name]
177 191
178 return inst_maps[(required or optional)[0]][None] 192 return inst_maps[(required or optional)[0]][None]
179 193
180 194
181 def MapDependencies(dep_map, inst_map): 195 def map_dependencies(dep_map, inst_map):
182 # NOTE: this is 'inefficient', but correct and compact. 196 # NOTE: this is 'inefficient', but correct and compact.
183 dep_map = copy.deepcopy(dep_map) 197 dep_map = copy.deepcopy(dep_map)
184 while dep_map: 198 while dep_map:
185 did_something = False 199 did_something = False
186 to_pop = [] 200 to_pop = []
187 for api_name, deps in dep_map.iteritems(): 201 for api_name, deps in dep_map.iteritems():
188 to_remove = [] 202 to_remove = []
189 for dep in [d for d in deps if d not in dep_map]: 203 for dep in [d for d in deps if d not in dep_map]:
190 # Grab the injection site 204 # Grab the injection site
191 obj = inst_map[api_name].m 205 obj = inst_map[api_name].m
192 assert not hasattr(obj, dep) 206 assert not hasattr(obj, dep)
193 setattr(obj, dep, inst_map[dep]) 207 setattr(obj, dep, inst_map[dep])
194 to_remove.append(dep) 208 to_remove.append(dep)
195 did_something = True 209 did_something = True
196 map(deps.remove, to_remove) 210 map(deps.remove, to_remove)
197 if not deps: 211 if not deps:
198 to_pop.append(api_name) 212 to_pop.append(api_name)
199 did_something = True 213 did_something = True
200 map(dep_map.pop, to_pop) 214 map(dep_map.pop, to_pop)
201 assert did_something, 'Did nothing on this loop. %s' % dep_map 215 assert did_something, 'Did nothing on this loop. %s' % dep_map
202 216
203 217
204 def CreateTestApi(names): 218 def create_test_api(names):
205 return CreateApi(MODULE_DIRS(), names, optional=('TEST_API', RecipeTestApi)) 219 return create_api(MODULE_DIRS(), names, optional=('TEST_API', RecipeTestApi))
206 220
207 221
208 def CreateRecipeApi(names, test_data=DisabledTestData(), **kwargs): 222 def create_recipe_api(names, test_data=DisabledTestData(), **kwargs):
209 return CreateApi(MODULE_DIRS(), names, test_data=test_data, kwargs=kwargs, 223 return create_api(MODULE_DIRS(), names, test_data=test_data, kwargs=kwargs,
210 required=('API', RecipeApi), 224 required=('API', RecipeApi),
211 optional=('TEST_API', RecipeTestApi)) 225 optional=('TEST_API', RecipeTestApi))
212 226
213 227
214 def Cached(f): 228 def cached(f):
iannucci 2014/03/05 20:43:37 maybe this should move to recipe_util.py? Probabl
Vadim Sh. 2014/03/05 21:40:38 Done.
215 """Caches/memoizes a unary function. 229 """Caches/memoizes an unary function.
216 230
217 If the function throws an exception, the cache table will not be updated. 231 If the function throws an exception, the cache table will not be updated.
218 """ 232 """
219 cache = {} 233 cache = {}
220 empty = object() 234 empty = object()
221 235
222 @wraps(f) 236 @wraps(f)
223 def cached_f(inp): 237 def cached_f(inp):
224 cache_entry = cache.get(inp, empty) 238 cache_entry = cache.get(inp, empty)
225 if cache_entry is empty: 239 if cache_entry is empty:
226 cache_entry = f(inp) 240 cache_entry = f(inp)
227 cache[inp] = cache_entry 241 cache[inp] = cache_entry
228 return cache_entry 242 return cache_entry
229 return cached_f 243 return cached_f
230 244
231 245
232 class NoSuchRecipe(Exception): 246 class NoSuchRecipe(Exception):
233 pass 247 pass
234 248
235 249
236 class ModuleObject(object): 250 class ModuleObject(object):
237 def __init__(self, d): 251 def __init__(self, d):
238 for k, v in d.iteritems(): 252 for k, v in d.iteritems():
239 setattr(self, k, v) 253 setattr(self, k, v)
240 254
241 255
242 @Cached 256 @cached
243 def LoadRecipe(recipe): 257 def load_recipe(recipe):
244 # If the recipe is specified as "module:recipe", then it is an recipe 258 # If the recipe is specified as "module:recipe", then it is an recipe
245 # contained in a recipe_module as an example. Look for it in the modules 259 # contained in a recipe_module as an example. Look for it in the modules
246 # imported by load_recipe_modules instead of the normal search paths. 260 # imported by load_recipe_modules instead of the normal search paths.
247 if ':' in recipe: 261 if ':' in recipe:
248 module_name, example = recipe.split(':') 262 module_name, example = recipe.split(':')
249 assert example.endswith('example') 263 assert example.endswith('example')
250 RECIPE_MODULES = load_recipe_modules(MODULE_DIRS()) 264 RECIPE_MODULES = load_recipe_modules(MODULE_DIRS())
251 try: 265 try:
252 return getattr(getattr(RECIPE_MODULES, module_name), example) 266 return getattr(getattr(RECIPE_MODULES, module_name), example)
253 except AttributeError: 267 except AttributeError:
(...skipping 21 matching lines...) Expand all
275 def loop_over_recipes(): 289 def loop_over_recipes():
276 for path in RECIPE_DIRS(): 290 for path in RECIPE_DIRS():
277 for recipe in find_recipes( 291 for recipe in find_recipes(
278 path, lambda f: f.endswith('.py') and f[0] != '_'): 292 path, lambda f: f.endswith('.py') and f[0] != '_'):
279 yield recipe, recipe[len(path)+1:-len('.py')] 293 yield recipe, recipe[len(path)+1:-len('.py')]
280 for path in MODULE_DIRS(): 294 for path in MODULE_DIRS():
281 for recipe in find_recipes( 295 for recipe in find_recipes(
282 path, lambda f: f.endswith('example.py')): 296 path, lambda f: f.endswith('example.py')):
283 module_name = os.path.dirname(recipe)[len(path)+1:] 297 module_name = os.path.dirname(recipe)[len(path)+1:]
284 yield recipe, '%s:example' % module_name 298 yield recipe, '%s:example' % module_name
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698