1#!/usr/bin/python 2 3# 4# Copyright 2018, The Android Open Source Project 5# 6# Licensed under the Apache License, Version 2.0 (the "License"); 7# you may not use this file except in compliance with the License. 8# You may obtain a copy of the License at 9# 10# http://www.apache.org/licenses/LICENSE-2.0 11# 12# Unless required by applicable law or agreed to in writing, software 13# distributed under the License is distributed on an "AS IS" BASIS, 14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15# See the License for the specific language governing permissions and 16# limitations under the License. 17# 18 19import EddParser 20from PFWScriptGenerator import PfwScriptTranslator 21import hostConfig 22 23import argparse 24import re 25import sys 26import tempfile 27import os 28import logging 29import subprocess 30import xml.etree.ElementTree as ET 31 32# 33# In order to build the XML Settings file at build time, an instance of the parameter-framework 34# shall be started and fed with all the criterion types/criteria that will be used by 35# the engineconfigurable. 36# This scripts allows generates the settings from the same audio_criterion_types.xml / 37# audio_criteria.xml files used at run time by the engineconfigurable 38# 39 40def parseArgs(): 41 argparser = argparse.ArgumentParser(description="Parameter-Framework XML \ 42 Settings file generator.\n\ 43 Exit with the number of (recoverable or not) error that occured.") 44 argparser.add_argument('--toplevel-config', 45 help="Top-level parameter-framework configuration file. Mandatory.", 46 metavar="TOPLEVEL_CONFIG_FILE", 47 required=True) 48 argparser.add_argument('--criteria', 49 help="Criteria file, in XML format: \ 50 in '<criteria> \ 51 <criterion name="" type=""/> \ 52 </criteria>' \ 53 format. Mandatory.", 54 metavar="CRITERIA_FILE", 55 type=argparse.FileType('r'), 56 required=True) 57 argparser.add_argument('--criteriontypes', 58 help="Criterion types XML file, in \ 59 '<criterion_types> \ 60 <criterion_type name="" type=<inclusive|exclusive> values=<value1,value2,...>/> \ 61 </criterion_types>' \ 62 format. Mandatory.", 63 metavar="CRITERION_TYPE_FILE", 64 type=argparse.FileType('r'), 65 required=False) 66 argparser.add_argument('--initial-settings', 67 help="Initial XML settings file (containing a \ 68 <ConfigurableDomains> tag", 69 nargs='?', 70 default=None, 71 metavar="XML_SETTINGS_FILE") 72 argparser.add_argument('--add-domains', 73 help="List of single domain files (each containing a single \ 74 <ConfigurableDomain> tag", 75 metavar="XML_DOMAIN_FILE", 76 nargs='*', 77 dest='xml_domain_files', 78 default=[]) 79 argparser.add_argument('--add-edds', 80 help="List of files in EDD syntax (aka \".pfw\" files)", 81 metavar="EDD_FILE", 82 type=argparse.FileType('r'), 83 nargs='*', 84 default=[], 85 dest='edd_files') 86 argparser.add_argument('--schemas-dir', 87 help="Directory of parameter-framework XML Schemas for generation \ 88 validation", 89 default=None) 90 argparser.add_argument('--target-schemas-dir', 91 help="Ignored. Kept for retro-compatibility") 92 argparser.add_argument('--validate', 93 help="Validate the settings against XML schemas", 94 action='store_true') 95 argparser.add_argument('--verbose', 96 action='store_true') 97 98 return argparser.parse_args() 99 100# 101# Parses audio_criterion_types.xml / audio_criteria.xml files used at run time by the 102# engineconfigurable and outputs a dictionnary of criteria. 103# For each criteria, the name, type (aka inclusive (bitfield) or exclusive (enum), the values 104# are provided. 105# 106def parseCriteriaAndCriterionTypes(criteriaFile, criterionTypesFile): 107 # Parse criteria and criterion types XML files 108 # 109 criteria_tree = ET.parse(criteriaFile) 110 logging.info("Importing criteriaFile {}".format(criteriaFile)) 111 criterion_types_tree = ET.parse(criterionTypesFile) 112 logging.info("Importing criterionTypesFile {}".format(criterionTypesFile)) 113 114 criteria_root = criteria_tree.getroot() 115 criterion_types_root = criterion_types_tree.getroot() 116 117 all_criteria = [] 118 for criterion in criteria_root.findall('criterion'): 119 criterion_name = criterion.get('name') 120 type_name = criterion.get('type') 121 logging.info("Importing criterion_name {}".format(criterion_name)) 122 logging.info("Importing type_name {}".format(type_name)) 123 124 for criterion_types in criterion_types_tree.findall('criterion_type'): 125 criterion_type_name = criterion_types.get('name') 126 if criterion_type_name == type_name: 127 criterion_inclusiveness = criterion_types.get('type') 128 129 criterion_values = [] 130 131 values_node = criterion_types.find('values') 132 if values_node is not None: 133 for value in values_node.findall('value'): 134 criterion_values.append(value.get('literal')) 135 136 if len(criterion_values) == 0: 137 criterion_values.append('') 138 139 logging.info("Importing criterion_type_name {}".format(criterion_type_name)) 140 logging.info("Importing criterion_inclusiveness {}".format(criterion_inclusiveness)) 141 logging.info("Importing criterion_values {}".format(criterion_values)) 142 143 all_criteria.append({ 144 "name" : criterion_name, 145 "inclusive" : criterion_inclusiveness, 146 "values" : criterion_values}) 147 break 148 149 return all_criteria 150 151# 152# Parses the Edd files (aka .pfw extension file), which is a simplified language to write the 153# parameter framework settings. 154# 155def parseEdd(EDDFiles): 156 parsed_edds = [] 157 158 for edd_file in EDDFiles: 159 try: 160 root = EddParser.Parser().parse(edd_file) 161 except EddParser.MySyntaxError as ex: 162 logging.critical(str(ex)) 163 logging.info("EXIT ON FAILURE") 164 exit(2) 165 166 try: 167 root.propagate() 168 except EddParser.MyPropagationError, ex : 169 logging.critical(str(ex)) 170 logging.info("EXIT ON FAILURE") 171 exit(1) 172 173 parsed_edds.append((edd_file.name, root)) 174 return parsed_edds 175 176# 177# Generates all the required commands to be sent to the instance of parameter-framework launched 178# at runtime to generate the XML Settings file. 179# It takes as input the collection of criteria, the domains and the simplified settings read from 180# pfw. 181# 182def generateDomainCommands(logging, all_criteria, initial_settings, xml_domain_files, parsed_edds): 183 # create and inject all the criteria 184 logging.info("Creating all criteria") 185 for criterion in all_criteria: 186 yield ["createSelectionCriterion", criterion['inclusive'], 187 criterion['name']] + criterion['values'] 188 189 yield ["start"] 190 191 # Import initial settings file 192 if initial_settings: 193 logging.info("Importing initial settings file {}".format(initial_settings)) 194 yield ["importDomainsWithSettingsXML", initial_settings] 195 196 # Import each standalone domain files 197 for domain_file in xml_domain_files: 198 logging.info("Importing single domain file {}".format(domain_file)) 199 yield ["importDomainWithSettingsXML", domain_file] 200 201 # Generate the script for each EDD file 202 for filename, parsed_edd in parsed_edds: 203 logging.info("Translating and injecting EDD file {}".format(filename)) 204 translator = PfwScriptTranslator() 205 parsed_edd.translate(translator) 206 for command in translator.getScript(): 207 yield command 208 209# 210# Entry point of the domain generator. 211# -Parses Criterion types and criteria files 212# -Parses settings written in simplified pfw language. 213# -Launches a parameter-framework 214# -Translates the settings into command that can be interpreted by parameter-framework. 215# -Use the exports command and output them in XML Settings file. 216# 217def main(): 218 logging.root.setLevel(logging.INFO) 219 args = parseArgs() 220 221 all_criteria = parseCriteriaAndCriterionTypes(args.criteria, args.criteriontypes) 222 223 # 224 # EDD files (aka ".pfw" files) 225 # 226 parsed_edds = parseEdd(args.edd_files) 227 228 # We need to modify the toplevel configuration file to account for differences 229 # between development setup and target (installation) setup, in particular, the 230 # TuningMwith ode must be enforced, regardless of what will be allowed on the target 231 fake_toplevel_config = tempfile.NamedTemporaryFile(mode='w', delete=False, suffix=".xml", 232 prefix="TMPdomainGeneratorPFConfig_") 233 234 install_path = os.path.dirname(os.path.realpath(args.toplevel_config)) 235 hostConfig.configure( 236 infile=args.toplevel_config, 237 outfile=fake_toplevel_config, 238 structPath=install_path) 239 fake_toplevel_config.close() 240 241 # Create the connector. Pipe its input to us in order to write commands; 242 # connect its output to stdout in order to have it dump the domains 243 # there; connect its error output to stderr. 244 connector = subprocess.Popen(["domainGeneratorConnector", 245 fake_toplevel_config.name, 246 'verbose' if args.verbose else 'no-verbose', 247 'validate' if args.validate else 'no-validate', 248 args.schemas_dir], 249 stdout=sys.stdout, stdin=subprocess.PIPE, stderr=sys.stderr) 250 251 initial_settings = None 252 if args.initial_settings: 253 initial_settings = os.path.realpath(args.initial_settings) 254 255 for command in generateDomainCommands(logging, all_criteria, initial_settings, 256 args.xml_domain_files, parsed_edds): 257 connector.stdin.write('\0'.join(command)) 258 connector.stdin.write("\n") 259 260 # Closing the connector's input triggers the domain generation 261 connector.stdin.close() 262 connector.wait() 263 os.remove(fake_toplevel_config.name) 264 return connector.returncode 265 266# If this file is directly executed 267if __name__ == "__main__": 268 sys.exit(main()) 269