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