| 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 |