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