diff --git a/wa/framework/configuration/core.py b/wa/framework/configuration/core.py index 8bd8b3e2..189e87c9 100644 --- a/wa/framework/configuration/core.py +++ b/wa/framework/configuration/core.py @@ -629,7 +629,8 @@ class RunConfiguration(Configuration): name = "Run Configuration" - # Metadata is separated out because it is not loaded into the auto generated config file + # Metadata is separated out because it is not loaded into the auto + # generated config file meta_data = [ ConfigurationPoint('run_name', kind=str, description=''' @@ -908,7 +909,8 @@ class JobSpec(Configuration): except NotFoundError: global_runtime_params = {} for source in plugin_cache.sources: - runtime_parameters[source] = global_runtime_params[source] + if source in global_runtime_params: + runtime_parameters[source] = global_runtime_params[source] # Add runtime parameters from JobSpec for source, values in self.to_merge['runtime_parameters'].iteritems(): diff --git a/wa/framework/configuration/parsers.py b/wa/framework/configuration/parsers.py index 7f1a747c..d57b1bc8 100644 --- a/wa/framework/configuration/parsers.py +++ b/wa/framework/configuration/parsers.py @@ -32,6 +32,7 @@ class ConfigParser(object): def load(self, state, raw, source, wrap_exceptions=True): # pylint: disable=too-many-branches try: + state.plugin_cache.add_source(source) if 'run_name' in raw: msg = '"run_name" can only be specified in the config '\ 'section of an agenda' diff --git a/wa/framework/configuration/plugin_cache.py b/wa/framework/configuration/plugin_cache.py index bfabb97c..d8a3f8e8 100644 --- a/wa/framework/configuration/plugin_cache.py +++ b/wa/framework/configuration/plugin_cache.py @@ -181,47 +181,74 @@ class PluginCache(object): :rtype: A fully merged and validated configuration in the form of a obj_dict. """ - generic_config = copy(self.plugin_configs[generic_name]) - specific_config = copy(self.plugin_configs[specific_name]) - cfg_points = self.get_plugin_parameters(specific_name) + ms = MergeState() + ms.generic_name = generic_name + ms.specific_name = specific_name + ms.generic_config = copy(self.plugin_configs[generic_name]) + ms.specific_config = copy(self.plugin_configs[specific_name]) + ms.cfg_points = self.get_plugin_parameters(specific_name) sources = self.sources - seen_specific_config = defaultdict(list) # set_value uses the 'name' attribute of the passed object in it error # messages, to ensure these messages make sense the name will have to be # changed several times during this function. final_config.name = specific_name - # pylint: disable=too-many-nested-blocks for source in sources: try: - if source in generic_config: - final_config.name = generic_name - for name, cfg_point in cfg_points.iteritems(): - if name in generic_config[source]: - if name in seen_specific_config: - msg = ('"{generic_name}" configuration "{config_name}" has already been ' - 'specified more specifically for {specific_name} in:\n\t\t{sources}') - msg = msg.format(generic_name=generic_name, - config_name=name, - specific_name=specific_name, - sources=", ".join(seen_specific_config[name])) - raise ConfigError(msg) - value = generic_config[source][name] - cfg_point.set_value(final_config, value, check_mandatory=False) - - if source in specific_config: - final_config.name = specific_name - for name, cfg_point in cfg_points.iteritems(): - if name in specific_config[source]: - seen_specific_config[name].append(str(source)) - value = specific_config[source][name] - cfg_point.set_value(final_config, value, check_mandatory=False) - + update_config_from_source(final_config, source, ms) except ConfigError as e: raise ConfigError('Error in "{}":\n\t{}'.format(source, str(e))) # Validate final configuration final_config.name = specific_name - for cfg_point in cfg_points.itervalues(): + for cfg_point in ms.cfg_points.itervalues(): cfg_point.validate(final_config) + + +class MergeState(object): + + def __init__(self): + self.generic_name = None + self.specific_name = None + self.generic_config = None + self.specific_config = None + self.cfg_points = None + self.seen_specific_config = defaultdict(list) + + +def update_config_from_source(final_config, source, state): + if source in state.generic_config: + final_config.name = state.generic_name + for name, cfg_point in state.cfg_points.iteritems(): + if name in state.generic_config[source]: + if name in state.seen_specific_config: + msg = ('"{generic_name}" configuration "{config_name}" has ' + 'already been specified more specifically for ' + '{specific_name} in:\n\t\t{sources}') + seen_sources = state.seen_specific_config[name] + msg = msg.format(generic_name=generic_name, + config_name=name, + specific_name=specific_name, + sources=", ".join(seen_sources)) + raise ConfigError(msg) + value = state.generic_config[source].pop(name) + cfg_point.set_value(final_config, value, check_mandatory=False) + + if state.generic_config[source]: + msg = 'Unexected values for {}: {}' + raise ConfigError(msg.format(state.generic_name, + state.generic_config[source])) + + if source in state.specific_config: + final_config.name = state.specific_name + for name, cfg_point in state.cfg_points.iteritems(): + if name in state.specific_config[source]: + seen_state.specific_config[name].append(str(source)) + value = state.specific_config[source].pop(name) + cfg_point.set_value(final_config, value, check_mandatory=False) + + if state.specific_config[source]: + msg = 'Unexected values for {}: {}' + raise ConfigError(msg.format(state.specific_name, + state.specific_config[source])) diff --git a/wa/framework/target/descriptor.py b/wa/framework/target/descriptor.py index 34966367..c1252289 100644 --- a/wa/framework/target/descriptor.py +++ b/wa/framework/target/descriptor.py @@ -24,6 +24,35 @@ def get_target_descriptions(loader=pluginloader): return targets.values() +def instantiate_target(tdesc, params, connect=None): + target_params = {p.name: p for p in tdesc.target_params} + platform_params = {p.name: p for p in tdesc.platform_params} + conn_params = {p.name: p for p in tdesc.conn_params} + + tp, pp, cp = {}, {}, {} + + for name, value in params.iteritems(): + if name in target_params: + tp[name] = value + elif name in platform_params: + pp[name] = value + elif name in conn_params: + cp[name] = value + else: + msg = 'Unexpected parameter for {}: {}' + raise ValueError(msg.format(tdesc.name, name)) + + tp['platform'] = (tdesc.platform or Platform)(**pp) + if cp: + tp['connection_settings'] = cp + if tdesc.connection: + tp['conn_cls'] = tdesc.connection + if connect is not None: + tp['connect'] = connect + + return tdesc.target(**tp) + + class TargetDescription(object): def __init__(self, name, source, description=None, target=None, platform=None, @@ -86,6 +115,18 @@ COMMON_TARGET_PARAMS = [ Please see ``devlab`` documentation for information on the available modules. '''), + Parameter('load_default_modules', kind=bool, default=True, + description=''' + A number of modules (e.g. for working with the cpufreq subsystem) are + loaded by default when a Target is instantiated. Setting this to + ``True`` would suppress that, ensuring that only the base Target + interface is initialized. + + You may want to set this if there is a problem with one or more default + modules on your platform (e.g. your device is unrooted and cpufreq is + not accessible to unprivileged users), or if Target initialization is + taking too long for your platform. + '''), ] COMMON_PLATFORM_PARAMS = [ diff --git a/wa/framework/target/info.py b/wa/framework/target/info.py index 4341e155..f3e40119 100644 --- a/wa/framework/target/info.py +++ b/wa/framework/target/info.py @@ -1,6 +1,7 @@ from devlib import AndroidTarget from devlib.exception import TargetError from devlib.target import KernelConfig, KernelVersion, Cpuinfo +from devlib.utils.android import AndroidProperties class TargetInfo(object): @@ -21,8 +22,9 @@ class TargetInfo(object): if pod["target"] == "AndroidTarget": instance.screen_resolution = pod['screen_resolution'] - instance.prop = pod['prop'] - instance.prop = pod['android_id'] + instance.prop = AndroidProperties('') + instance.prop._properties = pod['prop'] + instance.android_id = pod['android_id'] return instance @@ -72,7 +74,7 @@ class TargetInfo(object): if self.target == "AndroidTarget": pod['screen_resolution'] = self.screen_resolution - pod['prop'] = self.prop + pod['prop'] = self.prop._properties pod['android_id'] = self.android_id return pod diff --git a/wa/framework/target/manager.py b/wa/framework/target/manager.py index 659516d6..545178e6 100644 --- a/wa/framework/target/manager.py +++ b/wa/framework/target/manager.py @@ -9,6 +9,8 @@ import sys from wa.framework import signal from wa.framework.exception import WorkerThreadError, ConfigError from wa.framework.plugin import Parameter +from wa.framework.target.descriptor import (get_target_descriptions, + instantiate_target) from wa.framework.target.info import TargetInfo from wa.framework.target.runtime_config import (SysfileValuesRuntimeConfig, HotplugRuntimeConfig, @@ -41,54 +43,26 @@ class TargetManager(object): """), ] - DEVICE_MAPPING = {'test' : {'platform_name':'generic', - 'target_name': 'android'}, - 'other': {'platform_name':'test', - 'target_name': 'linux'}, - } - runtime_config_cls = [ - # order matters - SysfileValuesRuntimeConfig, - HotplugRuntimeConfig, - CpufreqRuntimeConfig, - CpuidleRuntimeConfig, - ] + # order matters + SysfileValuesRuntimeConfig, + HotplugRuntimeConfig, + CpufreqRuntimeConfig, + CpuidleRuntimeConfig, + ] def __init__(self, name, parameters): - self.name = name + self.target_name = name self.target = None self.assistant = None - self.target_name = None self.platform_name = None self.parameters = parameters self.disconnect = parameters.get('disconnect') self.info = TargetInfo() - # Determine platform and target based on passed name - self._parse_name() - # Create target - self._get_target() - # Create an assistant to perform target specific configuration - self._get_assistant() - - ### HERE FOR TESTING, WILL BE CALLED EXTERNALLY ### - # Connect to device and retrieve details. - # self.initialize() - # self.add_parameters() - # self.validate_parameters() - # self.set_parameters() - - def initialize(self): + self._init_target() + self._init_assistant() self.runtime_configs = [cls(self.target) for cls in self.runtime_config_cls] - # if self.parameters: - # self.logger.info('Connecting to the device') - with signal.wrap('TARGET_CONNECT'): - self.target.connect() - # self.info.load(self.target) - # info_file = os.path.join(self.context.info_directory, 'target.json') - # with open(info_file, 'w') as wfh: - # json.dump(self.info.to_pod(), wfh) def finalize(self): # self.logger.info('Disconnecting from the device') @@ -108,10 +82,16 @@ class TargetManager(object): if any(parameter in name for parameter in cfg.supported_parameters): cfg.add(name, self.parameters.pop(name)) - def validate_parameters(self): + def get_target_info(self): + return TargetInfo(self.target) + + def validate_runtime_parameters(self, params): for cfg in self.runtime_configs: cfg.validate() + def merge_runtime_parameters(self, params): + pass + def set_parameters(self): for cfg in self.runtime_configs: cfg.set() @@ -120,47 +100,25 @@ class TargetManager(object): for cfg in self.runtime_configs: cfg.clear() - def _parse_name(self): - # Try and get platform and target - self.name = identifier(self.name.replace('-', '_')) - if '_' in self.name: - self.platform_name, self.target_name = self.name.split('_', 1) - elif self.name in self.DEVICE_MAPPING: - self.platform_name = self.DEVICE_MAPPING[self.name]['platform_name'] - self.target_name = self.DEVICE_MAPPING[self.name]['target_name'] - else: - raise ConfigError('Unknown Device Specified {}'.format(self.name)) + def _init_target(self): + target_map = {td.name: td for td in get_target_descriptions()} + if self.target_name not in target_map: + raise ValueError('Unknown Target: {}'.format(self.target_name)) + tdesc = target_map[self.target_name] + self.target = instantiate_target(tdesc, self.parameters, connect=False) + with signal.wrap('TARGET_CONNECT'): + self.target.connect() + self.target.setup() - def _get_target(self): - # Create a corresponding target and target-assistant - if self.target_name == 'android': - self.target = AndroidTarget() - elif self.target_name == 'linux': - self.target = LinuxTarget() # pylint: disable=redefined-variable-type - elif self.target_name == 'localLinux': - self.target = LocalLinuxTarget() - else: - raise ConfigError('Unknown Target Specified {}'.format(self.target_name)) - - def _get_assistant(self): - # Create a corresponding target and target-assistant to help with platformy stuff? - if self.target_name == 'android': + def _init_assistant(self): + # Create a corresponding target and target-assistant to help with + # platformy stuff? + if self.target.os == 'android': self.assistant = AndroidAssistant(self.target) - elif self.target_name in ['linux', 'localLinux']: + elif self.target.os == 'linux': self.assistant = LinuxAssistant(self.target) # pylint: disable=redefined-variable-type else: - raise ConfigError('Unknown Target Specified {}'.format(self.target_name)) - - # def validate_runtime_parameters(self, parameters): - # for name, value in parameters.iteritems(): - # self.add_parameter(name, value) - # self.validate_parameters() - - # def set_runtime_parameters(self, parameters): - # # self.clear() - # for name, value in parameters.iteritems(): - # self.add_parameter(name, value) - # self.set_parameters() + raise ValueError('Unknown Target OS: {}'.format(self.target.os)) class LinuxAssistant(object): diff --git a/wa/utils/log.py b/wa/utils/log.py index 906116e6..a14d88ff 100644 --- a/wa/utils/log.py +++ b/wa/utils/log.py @@ -80,7 +80,7 @@ def set_level(level): def add_file(filepath, level=logging.DEBUG, - fmt='%(asctime)s %(levelname)-8s %(name)10.10s: %(message)s'): + fmt='%(asctime)s %(levelname)-8s %(name)s: %(message)-10.10s'): root_logger = logging.getLogger() file_handler = logging.FileHandler(filepath) file_handler.setLevel(level)