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 collections | 5 import collections |
6 import contextlib | 6 import contextlib |
7 import imp | 7 import imp |
8 import inspect | 8 import inspect |
9 import os | 9 import os |
10 import sys | 10 import sys |
(...skipping 113 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
124 # TODO(luqui): Forbid depending on a module from a (locally) undeclared | 124 # TODO(luqui): Forbid depending on a module from a (locally) undeclared |
125 # dependency. | 125 # dependency. |
126 def __init__(self, package, module, local_name, universe): | 126 def __init__(self, package, module, local_name, universe): |
127 mod_path = ( | 127 mod_path = ( |
128 universe.package_deps.get_package(package).module_path(module)) | 128 universe.package_deps.get_package(package).module_path(module)) |
129 super(PackageDependency, self).__init__( | 129 super(PackageDependency, self).__init__( |
130 mod_path, local_name, universe=universe) | 130 mod_path, local_name, universe=universe) |
131 | 131 |
132 | 132 |
133 class RecipeUniverse(object): | 133 class RecipeUniverse(object): |
134 def __init__(self, package_deps): | 134 def __init__(self, package_deps, config_file): |
135 self._loaded = {} | 135 self._loaded = {} |
136 self._package_deps = package_deps | 136 self._package_deps = package_deps |
| 137 self._config_file = config_file |
| 138 |
| 139 @property |
| 140 def config_file(self): |
| 141 return self._config_file |
137 | 142 |
138 @property | 143 @property |
139 def module_dirs(self): | 144 def module_dirs(self): |
140 return self._package_deps.all_module_dirs | 145 return self._package_deps.all_module_dirs |
141 | 146 |
142 @property | 147 @property |
143 def recipe_dirs(self): | 148 def recipe_dirs(self): |
144 return self._package_deps.all_recipe_dirs | 149 return self._package_deps.all_recipe_dirs |
145 | 150 |
146 @property | 151 @property |
(...skipping 252 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
399 self._instances = {} | 404 self._instances = {} |
400 | 405 |
401 def instantiate(self, mod): | 406 def instantiate(self, mod): |
402 if mod in self._instances: | 407 if mod in self._instances: |
403 return self._instances[mod] | 408 return self._instances[mod] |
404 deps_dict = { name: self.instantiate(dep) | 409 deps_dict = { name: self.instantiate(dep) |
405 for name, dep in mod.LOADED_DEPS.iteritems() } | 410 for name, dep in mod.LOADED_DEPS.iteritems() } |
406 self._instances[mod] = self._instantiator(mod, deps_dict) | 411 self._instances[mod] = self._instantiator(mod, deps_dict) |
407 return self._instances[mod] | 412 return self._instances[mod] |
408 | 413 |
| 414 def _invoke_with_properties(callable_obj, all_props, prop_defs, arg_names, |
| 415 **additional_args): |
| 416 """Internal version of invoke_with_properties. |
| 417 |
| 418 The main difference is it gets passed the argument names in as an argument. |
| 419 This allows us to reuse this logic elsewhere, without defining a fake function |
| 420 which has arbitrary argument names. |
| 421 """ |
| 422 for name, prop in prop_defs.items(): |
| 423 if not isinstance(prop, BoundProperty): |
| 424 raise ValueError( |
| 425 "You tried to invoke {} with an unbound Property {} named {}".format( |
| 426 callable, prop, name)) |
| 427 |
| 428 props = [] |
| 429 for arg in arg_names: |
| 430 if arg in additional_args: |
| 431 props.append(additional_args.pop(arg)) |
| 432 continue |
| 433 |
| 434 if arg not in prop_defs: |
| 435 raise UndefinedPropertyException( |
| 436 "Missing property definition for '{}'.".format(arg)) |
| 437 |
| 438 prop = prop_defs[arg] |
| 439 props.append(prop.interpret(all_props.get( |
| 440 prop.param_name, PROPERTY_SENTINEL))) |
| 441 |
| 442 return callable_obj(*props, **additional_args) |
| 443 |
409 def invoke_with_properties(callable_obj, all_props, prop_defs, | 444 def invoke_with_properties(callable_obj, all_props, prop_defs, |
410 **additional_args): | 445 **additional_args): |
411 """ | 446 """ |
412 Invokes callable with filtered, type-checked properties. | 447 Invokes callable with filtered, type-checked properties. |
413 | 448 |
414 Args: | 449 Args: |
415 callable_obj: The function to call, or class to instantiate. | 450 callable_obj: The function to call, or class to instantiate. |
416 This supports passing in either RunSteps, or a recipe module, | 451 This supports passing in either RunSteps, or a recipe module, |
417 which is a class. | 452 which is a class. |
418 all_props: A dictionary containing all the properties (instances of BoundPro
perty) | 453 all_props: A dictionary containing all the properties (instances of BoundPro
perty) |
419 currently defined in the system. | 454 currently defined in the system. |
420 prop_defs: A dictionary of name to property definitions for this callable. | 455 prop_defs: A dictionary of name to property definitions for this callable. |
421 additional_args: kwargs to pass through to the callable. | 456 additional_args: kwargs to pass through to the callable. |
422 Note that the names of the arguments can correspond to | 457 Note that the names of the arguments can correspond to |
423 positional arguments as well. | 458 positional arguments as well. |
424 | 459 |
425 Returns: | 460 Returns: |
426 The result of calling callable with the filtered properties | 461 The result of calling callable with the filtered properties |
427 and additional arguments. | 462 and additional arguments. |
428 """ | 463 """ |
429 # Check that we got passed BoundProperties, and not Properties | 464 # Check that we got passed BoundProperties, and not Properties |
430 | 465 |
431 for name, prop in prop_defs.items(): | |
432 if not isinstance(prop, BoundProperty): | |
433 raise ValueError( | |
434 "You tried to invoke {} with an unbound Property {} named {}".format( | |
435 callable, prop, name)) | |
436 | 466 |
437 # To detect when they didn't specify a property that they have as a | 467 # To detect when they didn't specify a property that they have as a |
438 # function argument, list the arguments, through inspection, | 468 # function argument, list the arguments, through inspection, |
439 # and then comparing this list to the provided properties. We use a list | 469 # and then comparing this list to the provided properties. We use a list |
440 # instead of a dict because getargspec returns a list which we would have to | 470 # instead of a dict because getargspec returns a list which we would have to |
441 # convert to a dictionary, and the benefit of the dictionary is pretty small. | 471 # convert to a dictionary, and the benefit of the dictionary is pretty small. |
442 props = [] | |
443 if inspect.isclass(callable_obj): | 472 if inspect.isclass(callable_obj): |
444 arg_names = inspect.getargspec(callable_obj.__init__).args | 473 arg_names = inspect.getargspec(callable_obj.__init__).args |
445 | 474 |
446 arg_names.pop(0) | 475 arg_names.pop(0) |
447 else: | 476 else: |
448 arg_names = inspect.getargspec(callable_obj).args | 477 arg_names = inspect.getargspec(callable_obj).args |
| 478 return _invoke_with_properties(callable_obj, all_props, prop_defs, arg_names, |
| 479 **additional_args) |
449 | 480 |
450 for arg in arg_names: | |
451 if arg in additional_args: | |
452 props.append(additional_args.pop(arg)) | |
453 continue | |
454 | |
455 if arg not in prop_defs: | |
456 raise UndefinedPropertyException( | |
457 "Missing property definition for '{}'.".format(arg)) | |
458 | |
459 prop = prop_defs[arg] | |
460 props.append(prop.interpret(all_props.get( | |
461 prop.param_name, PROPERTY_SENTINEL))) | |
462 | |
463 return callable_obj(*props, **additional_args) | |
464 | 481 |
465 def create_recipe_api(toplevel_deps, engine, test_data=DisabledTestData()): | 482 def create_recipe_api(toplevel_deps, engine, test_data=DisabledTestData()): |
466 def instantiator(mod, deps): | 483 def instantiator(mod, deps): |
467 kwargs = { | 484 kwargs = { |
468 'module': mod, | 485 'module': mod, |
469 'engine': engine, | 486 'engine': engine, |
470 # TODO(luqui): test_data will need to use canonical unique names. | 487 # TODO(luqui): test_data will need to use canonical unique names. |
471 'test_data': test_data.get_module_test_data(mod.NAME) | 488 'test_data': test_data.get_module_test_data(mod.NAME) |
472 } | 489 } |
473 prop_defs = mod.PROPERTIES | 490 prop_defs = mod.PROPERTIES |
(...skipping 22 matching lines...) Expand all Loading... |
496 return modapi | 513 return modapi |
497 | 514 |
498 mapper = DependencyMapper(instantiator) | 515 mapper = DependencyMapper(instantiator) |
499 api = RecipeTestApi(module=None) | 516 api = RecipeTestApi(module=None) |
500 for k,v in toplevel_deps.iteritems(): | 517 for k,v in toplevel_deps.iteritems(): |
501 setattr(api, k, mapper.instantiate(v)) | 518 setattr(api, k, mapper.instantiate(v)) |
502 return api | 519 return api |
503 | 520 |
504 | 521 |
505 | 522 |
OLD | NEW |