1#! /usr/bin/python3
2#
3# Copyright (c) 2011-2015, 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 argparse
32import re
33import sys
34import tempfile
35import os
36import logging
37import subprocess
38
39import EddParser
40from PFWScriptGenerator import PfwScriptTranslator
41import hostConfig
42
43
44def parseArgs():
45    argparser = argparse.ArgumentParser(description="Parameter-Framework XML \
46                                        Settings file generator.\n\
47                                        Exit with the number of (recoverable or not) \
48                                        error that occured.")
49    argparser.add_argument('--toplevel-config',
50                           help="Top-level parameter-framework configuration file. Mandatory.",
51                           metavar="TOPLEVEL_CONFIG_FILE",
52                           required=True)
53    argparser.add_argument('--criteria',
54                           help="Criteria file, in '<type> <name> : <value> <value...>' \
55                           format. Mandatory.",
56                           metavar="CRITERIA_FILE",
57                           type=argparse.FileType('r'),
58                           required=True)
59    argparser.add_argument('--initial-settings',
60                           help="Initial XML settings file (containing a \
61                           <ConfigurableDomains>  tag",
62                           nargs='?',
63                           default=None,
64                           metavar="XML_SETTINGS_FILE")
65    argparser.add_argument('--add-domains',
66                           help="List of single domain files (each containing a single \
67                           <ConfigurableDomain> tag",
68                           metavar="XML_DOMAIN_FILE",
69                           nargs='*',
70                           dest='xml_domain_files',
71                           default=[])
72    argparser.add_argument('--add-edds',
73                           help="List of files in EDD syntax (aka \".pfw\" files)",
74                           metavar="EDD_FILE",
75                           type=argparse.FileType('r'),
76                           nargs='*',
77                           default=[],
78                           dest='edd_files')
79    argparser.add_argument('--schemas-dir',
80                           help="Directory of parameter-framework XML Schemas for generation \
81                           validation",
82                           default=None)
83    argparser.add_argument('--target-schemas-dir',
84                           help="Ignored. Kept for retro-compatibility")
85    argparser.add_argument('--validate',
86                           help="Validate the settings against XML schemas",
87                           action='store_true')
88    argparser.add_argument('--verbose',
89                           action='store_true')
90
91    return argparser.parse_args()
92
93def parseCriteria(criteriaFile):
94    # Parse a criteria file
95    #
96    # This file define one criteria per line; they should respect this format:
97    #
98    # <type> <name> : <values>
99    #
100    # Where <type> is 'InclusiveCriterion' or 'ExclusiveCriterion';
101    #       <name> is any string w/o whitespace
102    #       <values> is a list of whitespace-separated values, each of which is any
103    #                string w/o a whitespace
104    criteria_pattern = re.compile(
105        r"^(?P<type>(?:Inclusive|Exclusive)Criterion)\s*" \
106        r"(?P<name>\S+)\s*:\s*" \
107        r"(?P<values>.*)$")
108    criterion_inclusiveness_table = {
109        'InclusiveCriterion' : "inclusive",
110        'ExclusiveCriterion' : "exclusive"}
111
112    all_criteria = []
113    for line_number, line in enumerate(criteriaFile):
114        match = criteria_pattern.match(line)
115        if not match:
116            raise ValueError("The following line is invalid: {}:{}\n{}".format(
117                criteriaFile.name, line_number, line))
118
119        criterion_name = match.groupdict()['name']
120        criterion_type = match.groupdict()['type']
121        criterion_values = re.split("\s*", match.groupdict()['values'])
122
123        criterion_inclusiveness = criterion_inclusiveness_table[criterion_type]
124
125        all_criteria.append({
126            "name" : criterion_name,
127            "inclusive" : criterion_inclusiveness,
128            "values" : criterion_values})
129
130    return all_criteria
131
132def parseEdd(EDDFiles):
133    parsed_edds = []
134
135    for edd_file in EDDFiles:
136        try:
137            root = EddParser.Parser().parse(edd_file)
138        except EddParser.MySyntaxError as ex:
139            logging.critical(str(ex))
140            logging.info("EXIT ON FAILURE")
141            exit(2)
142
143        try:
144            root.propagate()
145        except EddParser.MyPropagationError as ex:
146            logging.critical(str(ex))
147            logging.info("EXIT ON FAILURE")
148            exit(1)
149
150        parsed_edds.append((edd_file.name, root))
151    return parsed_edds
152
153def generateDomainCommands(logger, all_criteria, initial_settings, xml_domain_files, parsed_edds):
154    # create and inject all the criteria
155    logger.info("Creating all criteria")
156    for criterion in all_criteria:
157        yield ["createSelectionCriterion", criterion['inclusive'],
158               criterion['name']] + criterion['values']
159
160    yield ["start"]
161
162    # Import initial settings file
163    if initial_settings:
164        logger.info("Importing initial settings file {}".format(initial_settings))
165        yield ["importDomainsWithSettingsXML", initial_settings]
166
167    # Import each standalone domain files
168    for domain_file in xml_domain_files:
169        logger.info("Importing single domain file {}".format(domain_file))
170        yield ["importDomainWithSettingsXML", domain_file]
171
172    # Generate the script for each EDD file
173    for filename, parsed_edd in parsed_edds:
174        logger.info("Translating and injecting EDD file {}".format(filename))
175        translator = PfwScriptTranslator()
176        parsed_edd.translate(translator)
177        for command in translator.getScript():
178            yield command
179
180def main():
181    logging.root.setLevel(logging.INFO)
182    args = parseArgs()
183
184    all_criteria = parseCriteria(args.criteria)
185
186    #
187    # EDD files (aka ".pfw" files)
188    #
189    parsed_edds = parseEdd(args.edd_files)
190
191    # We need to modify the toplevel configuration file to account for differences
192    # between development setup and target (installation) setup, in particular, the
193    # TuningMwith ode must be enforced, regardless of what will be allowed on the target
194    fake_toplevel_config = tempfile.NamedTemporaryFile(mode='w', delete=False, suffix=".xml",
195                                                       prefix="TMPdomainGeneratorPFConfig_")
196
197    install_path = os.path.dirname(os.path.realpath(args.toplevel_config))
198    hostConfig.configure(infile=args.toplevel_config,
199                         outfile=fake_toplevel_config,
200                         structPath=install_path)
201    fake_toplevel_config.close()
202
203    # Create the connector. Pipe its input to us in order to write commands;
204    # connect its output to stdout in order to have it dump the domains
205    # there; connect its error output to stderr.
206    connector = subprocess.Popen(["domainGeneratorConnector",
207                                  fake_toplevel_config.name,
208                                  'verbose' if args.verbose else 'no-verbose',
209                                  'validate' if args.validate else 'no-validate',
210                                  args.schemas_dir],
211                                 stdout=sys.stdout, stdin=subprocess.PIPE, stderr=sys.stderr)
212
213    initial_settings = None
214    if args.initial_settings:
215        initial_settings = os.path.realpath(args.initial_settings)
216
217    for command in generateDomainCommands(logging, all_criteria, initial_settings,
218                                          args.xml_domain_files, parsed_edds):
219        connector.stdin.write('\0'.join(command))
220        connector.stdin.write("\n")
221
222    # Closing the connector's input triggers the domain generation
223    connector.stdin.close()
224    connector.wait()
225    os.remove(fake_toplevel_config.name)
226    return connector.returncode
227
228# If this file is directly executed
229if __name__ == "__main__":
230    exit(main())
231