1#! /usr/bin/python 2# 3# Copyright (c) 2011-2014, Intel Corporation 4# All rights reserved. 5# 6# Redistribution and use in source and binary forms, with or without modification, 7# are permitted provided that the following conditions are met: 8# 9# 1. Redistributions of source code must retain the above copyright notice, this 10# list of conditions and the following disclaimer. 11# 12# 2. Redistributions in binary form must reproduce the above copyright notice, 13# this list of conditions and the following disclaimer in the documentation and/or 14# other materials provided with the distribution. 15# 16# 3. Neither the name of the copyright holder nor the names of its contributors 17# may be used to endorse or promote products derived from this software without 18# specific prior written permission. 19# 20# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 21# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 22# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 24# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 25# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 26# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 27# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 29# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 31import PyPfw 32import EddParser 33from PfwBaseTranslator import PfwBaseTranslator, PfwException 34import hostConfig 35 36import argparse 37import re 38import sys 39import tempfile 40import os 41import logging 42 43def wrap_pfw_error_semantic(func): 44 def wrapped(*args, **kwargs): 45 ok, error = func(*args, **kwargs) 46 if not ok: 47 raise PfwException(error) 48 49 return wrapped 50 51class PfwTranslator(PfwBaseTranslator): 52 """Generates calls to the Pfw's python bindings""" 53 54 def __init__(self, pfw_instance, error_handler): 55 super(PfwTranslator, self).__init__() 56 self._pfw = pfw_instance 57 self._error_handler = error_handler 58 59 def _handleException(self, ex): 60 if isinstance(ex, PfwException): 61 # catch and handle translation errors... 62 self._error_handler(ex, self._getContext()) 63 else: 64 # ...but let any other error fall through 65 raise ex 66 67 @wrap_pfw_error_semantic 68 def _doCreateDomain(self, name): 69 return self._pfw.createDomain(name) 70 71 @wrap_pfw_error_semantic 72 def _doSetSequenceAware(self): 73 return self._pfw.setSequenceAwareness(self._ctx_domain, True) 74 75 @wrap_pfw_error_semantic 76 def _doAddElement(self, path): 77 return self._pfw.addConfigurableElementToDomain(self._ctx_domain, path) 78 79 @wrap_pfw_error_semantic 80 def _doCreateConfiguration(self, name): 81 return self._pfw.createConfiguration(self._ctx_domain, name) 82 83 @wrap_pfw_error_semantic 84 def _doSetElementSequence(self, paths): 85 return self._pfw.setElementSequence(self._ctx_domain, self._ctx_configuration, paths) 86 87 @wrap_pfw_error_semantic 88 def _doSetRule(self, rule): 89 return self._pfw.setApplicationRule(self._ctx_domain, self._ctx_configuration, rule) 90 91 @wrap_pfw_error_semantic 92 def _doSetParameter(self, path, value): 93 ok, _, error = self._pfw.accessConfigurationValue( 94 self._ctx_domain, self._ctx_configuration, path, value, True) 95 96 return ok, error 97 98 99class PfwTranslationErrorHandler: 100 def __init__(self): 101 self._errors = [] 102 self._hasFailed = False 103 104 def __call__(self, error, context): 105 sys.stderr.write("Error in context {}:\n\t{}\n".format(context, error)) 106 self._hasFailed = True 107 108 def hasFailed(self): 109 return self._hasFailed 110 111class PfwLogger(PyPfw.ILogger): 112 def __init__(self): 113 super(PfwLogger, self).__init__() 114 self.__logger = logging.root.getChild("parameter-framework") 115 116 def log(self, is_warning, message): 117 log_func = self.__logger.warning if is_warning else self.__logger.info 118 log_func(message) 119 120# If this file is directly executed 121if __name__ == "__main__": 122 logging.root.setLevel(logging.INFO) 123 124 argparser = argparse.ArgumentParser(description="Parameter-Framework XML \ 125 Settings file generator") 126 argparser.add_argument('--toplevel-config', 127 help="Top-level parameter-framework configuration file. Mandatory.", 128 metavar="TOPLEVEL_CONFIG_FILE", 129 required=True) 130 argparser.add_argument('--criteria', 131 help="Criteria file, in '<type> <name> : <value> <value...>' \ 132 format. Mandatory.", 133 metavar="CRITERIA_FILE", 134 type=argparse.FileType('r'), 135 required=True) 136 argparser.add_argument('--initial-settings', 137 help="Initial XML settings file (containing a \ 138 <ConfigurableDomains> tag", 139 nargs='?', 140 metavar="XML_SETTINGS_FILE") 141 argparser.add_argument('--add-domains', 142 help="List of single domain files (each containing a single \ 143 <ConfigurableDomain> tag", 144 metavar="XML_DOMAIN_FILE", 145 nargs='*', 146 dest='xml_domain_files', 147 default=[]) 148 argparser.add_argument('--add-edds', 149 help="List of files in EDD syntax (aka \".pfw\" files)", 150 metavar="EDD_FILE", 151 type=argparse.FileType('r'), 152 nargs='*', 153 default=[], 154 dest='edd_files') 155 argparser.add_argument('--schemas-dir', 156 help="Directory of parameter-framework XML Schemas for generation \ 157 validation", 158 default=None) 159 argparser.add_argument('--target-schemas-dir', 160 help="Directory of parameter-framework XML Schemas on target \ 161 machine (may be different than generating machine). \ 162 Defaults to \"Schemas\"", 163 default="Schemas") 164 argparser.add_argument('--validate', 165 help="Validate the settings against XML schemas", 166 action='store_true') 167 argparser.add_argument('--verbose', 168 action='store_true') 169 170 args = argparser.parse_args() 171 172 # 173 # Criteria file 174 # 175 # This file define one criteria per line; they should respect this format: 176 # 177 # <type> <name> : <values> 178 # 179 # Where <type> is 'InclusiveCriterion' or 'ExclusiveCriterion'; 180 # <name> is any string w/o whitespace 181 # <values> is a list of whitespace-separated values, each of which is any 182 # string w/o a whitespace 183 criteria_pattern = re.compile( 184 r"^(?P<type>(?:Inclusive|Exclusive)Criterion)\s*" \ 185 r"(?P<name>\S+)\s*:\s*" \ 186 r"(?P<values>.*)$") 187 criterion_inclusiveness_table = { 188 'InclusiveCriterion' : True, 189 'ExclusiveCriterion' : False} 190 all_criteria = [] 191 192 # Parse the criteria file 193 for line_number, line in enumerate(args.criteria, 1): 194 match = criteria_pattern.match(line) 195 if not match: 196 raise ValueError("The following line is invalid: {}:{}\n{}".format( 197 args.criteria.name, line_number, line)) 198 199 criterion_name = match.groupdict()['name'] 200 criterion_type = match.groupdict()['type'] 201 criterion_values = re.split("\s*", match.groupdict()['values']) 202 203 criterion_inclusiveness = criterion_inclusiveness_table[criterion_type] 204 205 all_criteria.append({ 206 "name" : criterion_name, 207 "inclusive" : criterion_inclusiveness, 208 "values" : criterion_values}) 209 210 # 211 # EDD files (aka ".pfw" files) 212 # 213 parsed_edds = [] 214 for edd_file in args.edd_files: 215 try: 216 root = parser = EddParser.Parser().parse(edd_file, args.verbose) 217 except EddParser.MySyntaxError as ex: 218 logging.critical(str(ex)) 219 logging.info("EXIT ON FAILURE") 220 exit(2) 221 222 try: 223 root.propagate() 224 except EddParser.MyPropagationError, ex : 225 logging.critical(str(ex)) 226 logging.info("EXIT ON FAILURE") 227 exit(1) 228 229 parsed_edds.append((edd_file.name, root)) 230 231 # We need to modify the toplevel configuration file to account for differences 232 # between development setup and target (installation) setup, in particular, the 233 # TuningMode must be enforced, regardless of what will be allowed on the target 234 with tempfile.NamedTemporaryFile(mode='w') as fake_toplevel_config: 235 install_path = os.path.dirname(os.path.realpath(args.toplevel_config)) 236 hostConfig.configure( 237 infile=args.toplevel_config, 238 outfile=fake_toplevel_config, 239 structPath=install_path) 240 fake_toplevel_config.flush() 241 242 # Create a new Pfw instance 243 pfw = PyPfw.ParameterFramework(fake_toplevel_config.name) 244 245 # create and inject all the criteria 246 logging.info("Creating all criteria") 247 for criterion in all_criteria: 248 criterion_type = pfw.createSelectionCriterionType(criterion['inclusive']) 249 250 for numerical, literal in enumerate(criterion['values']): 251 if criterion['inclusive']: 252 # inclusive criteria are "bitfields" 253 numerical = 1 << numerical 254 255 ok = criterion_type.addValuePair(numerical, literal) 256 if not ok: 257 logging.critical("valuepair {}/{} rejected for {}".format( 258 numerical, literal, criterion['name'])) 259 exit(1) 260 261 # we don't need the reference to the created criterion type; ignore the 262 # return value 263 pfw.createSelectionCriterion(criterion['name'], criterion_type) 264 265 # Set failure conditions 266 pfw.setFailureOnMissingSubsystem(False) 267 pfw.setFailureOnFailedSettingsLoad(False) 268 if args.validate: 269 pfw.setValidateSchemasOnStart(True) 270 if args.schemas_dir is not None: 271 schemas_dir = args.schemas_dir 272 else: 273 schemas_dir = os.path.join(install_path, "Schemas") 274 pfw.setSchemaFolderLocation(schemas_dir) 275 276 logger = PfwLogger() 277 pfw.setLogger(logger) 278 279 # Disable the remote interface because we don't need it and it might 280 # get in the way (e.g. the port is already in use) 281 pfw.setForceNoRemoteInterface(True) 282 283 # Finally, start the Pfw 284 ok, error = pfw.start() 285 if not ok: 286 logging.critical("Error while starting the pfw: {}".format(error)) 287 exit(1) 288 289 ok, error = pfw.setTuningMode(True) 290 if not ok: 291 logging.critical(error) 292 exit(1) 293 294 # Import initial settings file 295 if args.initial_settings: 296 initial_settings = os.path.realpath(args.initial_settings) 297 logging.info( 298 "Importing initial settings file {}".format(initial_settings)) 299 ok, error = pfw.importDomainsXml(initial_settings, True, True) 300 if not ok: 301 logging.critical(error) 302 exit(1) 303 304 # Import each standalone domain files 305 for domain_file in args.xml_domain_files: 306 logging.info("Importing single domain file {}".format(domain_file)) 307 ok, error = pfw.importSingleDomainXml(os.path.realpath(domain_file), 308 False, True, True) 309 if not ok: 310 logging.critical(error) 311 exit(1) 312 313 # Parse and inject each EDD file 314 error_handler = PfwTranslationErrorHandler() 315 translator = PfwTranslator(pfw, error_handler) 316 317 for filename, parsed_edd in parsed_edds: 318 logging.info("Translating and injecting EDD file {}".format(filename)) 319 parsed_edd.translate(translator) 320 if error_handler.hasFailed(): 321 logging.error("Error while importing parsed EDD files.\n") 322 exit(1) 323 324 # dirty hack: we change the schema location (right before exporting the 325 # domains) to their location on the target (which may be different than on 326 # the machine that is generating the domains) 327 pfw.setSchemaFolderLocation(args.target_schemas_dir) 328 329 # Export the resulting settings to the standard output 330 ok, domains, error = pfw.exportDomainsXml("", True, False) 331 sys.stdout.write(domains) 332