Merge changes to system model generator to SF tip.
#
# Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
# All rights reserved.
# This component and the accompanying materials are made available
# under the terms of "Eclipse Public License v1.0"
# which accompanies this distribution, and is available
# at the URL "http://www.eclipse.org/legal/epl-v10.html".
#
# Initial Contributors:
# Nokia Corporation - initial contribution.
#
# Contributors:
#
# Description:
#
import os, re, fnmatch
import logging
from optparse import OptionParser, OptionGroup
import cone_common
import time
from distutils.dir_util import mkpath, DistutilsFileError
from cone.public import api, plugin, utils, exceptions
from cone.report import generation_report
from cone.confml import persistentconfml
ROOT_PATH = os.path.dirname(os.path.abspath(__file__))
VERSION = '1.0'
log = logging.getLogger('cone')
def main():
""" Generate a configuration. """
parser = OptionParser(version="%%prog %s" % VERSION)
parser.add_options(cone_common.COMMON_OPTIONS)
parser.add_option("-c", "--configuration",\
dest="configuration",\
help="defines the name of the configuration for the action",\
metavar="CONFIG")
parser.add_option("-p", "--project",\
dest="project",\
help="defines the location of current project. Default is the current working directory.",\
default=".",\
metavar="STORAGE")
gen_group = OptionGroup(parser, 'Generate options',
'The generate function will create target files from a specific configuration.'\
'The generate will always work with read-only mode of the project, so no changes are saved to project')
gen_group.add_option("-o", "--output",\
dest="output",\
help="defines the target folder where the files are is generated or copied",\
metavar="FOLDER",\
default="output")
gen_group.add_option("-i", "--impl",\
dest="impls",\
action="append",
help=\
"""Define a Python regular expression filter for actual ImplML plugin(s) that needs to be executed. The whole path to ImplML filename is used in the regexp matching.
The impl operation can be used several times in a single command.
Example1 --impl crml => matches for any ImplML file that has a CrML string in the path.
Example2 --impl makeml$ => matches for ImplML file that has ends with MakeML string.
""",
metavar="IMPLS",\
default=None)
gen_group.add_option("--impl-tag",\
dest="tags",\
type="string",
action="append",
help="define a tag for the implementations that are included to the output. "\
"A tag is name value pair and has the following format: name:value, e.g. target:rofs3."\
"Example --impl-tag=target:uda --impl-tag=target:content, which would include impls include both tags.",
metavar="TAG",\
default=None)
gen_group.add_option("--impl-tag-policy",\
dest="tags_policy",\
type="string",
action="append",
help="Policy for implementation tags. May have one of the following values: --impl-tag-policy=AND, --impl-tag-policy=OR. "\
"Default is OR.",
metavar="TAGS_POLICY",\
default=None)
gen_group.add_option("-s", "--set",\
dest="overrides",\
action="append",
type="string",
help="Override a ConfML reference in the execution."\
"The set operation can be used several times in a single command."\
"Example -s foo.bar=10 -s foo.fea='test'.",
metavar="SET",\
default=None)
gen_group.add_option("--add",\
dest="added",\
action="append",
type="string",
help="Add a given configuration to the given configuration as last element."\
"The add operation can be used several times in a single command."\
"Example --add foo/root.confml --add bar/root-confml.",
metavar="CONF",\
default=None)
gen_group.add_option("-r", "--report",\
dest="report",\
action="store",
type="string",
help="Generates a report about settings that are properly generated."\
"Example -r report.html.",
metavar="FILE",\
default=None)
gen_group.add_option("--report-option",\
action="append",
help="Specifies the report verbose options, that defines "\
"what data is included to the report. The option can be "\
"used multiple times."\
"choises=[default|all]"\
"Example --report-option=all",
metavar="OPTION",\
default=[])
gen_group.add_option("-t", "--template",\
dest="template",\
action="store",
type="string",
help="Template used in report generation."\
"Example -t report_template.html.",
metavar="FILE",\
default=None)
gen_group.add_option("--report-data-output",\
type="string",
help="Specifies a file where intermediary report data is generated.",
metavar="FILE",\
default=None)
gen_group.add_option("-n", "--dryrun",\
dest="dryrun",\
action="store_true",
help="Executes generation without generation output.",
default=False)
gen_group.add_option("--add-setting-file",\
dest="settings",\
action="append",
type="string",
help="Generate specific settings in ini format."\
"Example -o my_generate_settings.cfg.",
metavar="FILE",\
default=None)
gen_group.add_option("--dump-autodata",\
dest="dump_autodata",\
action="store",
type="string",
metavar="FILE",
help="Specifies a confml file for storing autodata.confml permanently.",
default=None)
gen_group.add_option("-w", "--what",\
dest="what",\
action="store",
type="string",
metavar="FILE",
help="List output files to a txt file",
default=None)
lf_group = OptionGroup(parser, 'Layer filtering options',
'Layer filtering options define configuration layers to be used for filtering '\
'the implementations that are used to generate output. Filtering by a layer means that '\
'only implementations that generate their output based on settings changed on that layer '\
'are included in the generation.')
lf_group.add_option("-l", "--layer",\
dest="layers",\
type="int",
action="append",
help="Define a layer by giving its index in the root configuration. "\
"0 is first, 1 the second, -1 the last, -2 the second to last and so on. "\
"The layer operation can be used several times in a single command. "\
"Example -l -1 --layer=-2, which would append a layers -1 and -2 to the layers => layers = -1,-2",
metavar="LAYER",\
default=None)
lf_group.add_option("--layer-regex",
dest="layer_regexes",
action="append",
help="Define a regular expression for including layers into the generation process, "\
"e.g. --layer-regex layer[0-9]/root.confml. The pattern is matched against the layer root "\
"path, which could be e.g. 'assets/layer1/root.confml'.",
metavar="REGEX",)
lf_group.add_option("--layer-wildcard",
dest="layer_wildcards",
action="append",
help="Define a wildcard for including layers into the generation process, e.g "\
"--layer-wildcard layer*",
metavar="WILDCARD",)
lf_group.add_option("--all-layers",
dest="all_layers",
action="store_true",
help="Include all layers in generation. This switch overrides all other layer "\
"configurations (iMaker API and using the --layer, --layer-regex and --layer-wildcard parameters)",
default=False)
start_time = time.time()
parser.add_option_group(gen_group)
parser.add_option_group(lf_group)
(options, _) = parser.parse_args()
settinglist = [os.path.join(ROOT_PATH,'conesub_generate.cfg')]
if options.settings:
for setting_file in options.settings:
settinglist.append(os.path.normpath(os.path.join(ROOT_PATH, setting_file)))
gset = cone_common.get_settings(settinglist)
cone_common.handle_common_options(options, settings=gset)
current = api.Project(api.Storage.open(options.project,"r"))
active_root = current.get_storage().get_active_configuration()
if not options.configuration:
if active_root == "":
parser.error("configuration must be given")
else:
logging.getLogger('cone').info('No configuration given! Using active root configuration %s' % active_root)
options.configuration = active_root
try:
config = current.get_configuration(options.configuration)
except exceptions.NotFound:
parser.error("No such configuration: %s" % options.configuration)
reffilters = None
implfilters = None
impltags = None
# Include possible additional configurations
if options.added:
for configname in options.added:
logging.getLogger('cone').info('Adding configuration %s' % configname)
config.include_configuration(utils.resourceref.norm(configname))
# Get implementation filters from configuration
try:
implfilters = (config.get_default_view().get_feature('imakerapi.cone_impls').get_value() or '').split(',')
except exceptions.NotFound:
implfilters = []
# Get filters from command line if they exist => cmd overrides configuration
if options.impls:
implfilters = options.impls
if options.tags and len(options.tags) > 0:
impltags = {}
for tag in options.tags:
(name,value) = tag.split(':',2)
existingvalue = impltags.get(name,[])
existingvalue.append(value)
impltags[name] = existingvalue
logging.getLogger('cone').info('Tag filter %s' % impltags)
else:
impltags = None
tags_policy = 'OR'
if options.tags_policy:
tags_policy = options.tags_policy[0]
layerdefs = _get_included_layers(config, options, parser)
filter_by_refs = _filter_by_refs(config, options, parser)
if layerdefs:
logging.getLogger('cone').info('Included layers:\n%s' % '\n'.join(layerdefs))
else:
logging.getLogger('cone').info('Including all layers')
dview = config.get_default_view()
# Add data references if included layers are defined
if len(layerdefs) > 0:
# get the data references from given layers
logging.getLogger('cone').info('Getting layer specific data reference from %s' % layerdefs)
reffilters = []
for layer_path in utils.distinct_array(layerdefs):
logging.getLogger('cone').info('Searching layer %s' % layer_path)
layer = config.get_configuration(layer_path)
refs = _get_new_refs(reffilters, layer.list_leaf_datas())
# reduce the refs of sequences to single reference of the sequence feature
layerrefs = set()
for fea in dview.get_features(refs):
layerrefs.add(fea.fqr)
if fea.is_sequence():
layerrefs.add(fea.get_sequence_parent().fqr)
refs = sorted(list(layerrefs))
#logging.getLogger('cone').info("Refs from layer '%s'\n%s" % (layer.get_path(), '\n'.join(refs)))
reffilters += refs
# Make sure that the output folder exists
if not os.path.exists(options.output):
os.makedirs(options.output)
impls = plugin.filtered_impl_set(config,implfilters)
impls.output = options.output
log.info("Parsed %s implementation(s)" % len(impls))
logging.getLogger('cone').info("Supported implementation file extensions: %r" % plugin.get_supported_file_extensions())
# logging.getLogger('cone').debug('Loaded implementations:')
# for impl in impls:
# msg = "File '%s', impl. type '%s', class '%s', phase '%s'" % \
# (impl.ref, impl.IMPL_TYPE_ID, type(impl).__name__, impl.invocation_phase())
# logging.getLogger('cone').debug(msg)
# Create temporary variables
temp_feature_refs = impls.create_temp_features(config)
if reffilters is not None:
reffilters.extend(temp_feature_refs)
logging.getLogger('cone').info('Refs from temporary variables:\n%s' % '\n'.join(temp_feature_refs))
# Set overrides only after temp variables are created, so that
# they can also be modified from the command line
if options.overrides:
# Make sure that the last layer is the autodata layer
plugin.get_autoconfig(config)
for override in options.overrides:
(ref,value) = override.split('=',1)
config.get_default_view().get_feature(ref).set_value(value)
# ---------------
# Generate output
# ---------------
context = plugin.GenerationContext(configuration = config,
tags = impltags or {},
tags_policy = tags_policy,
output = options.output,
impl_set = impls,
temp_features = temp_feature_refs,
filter_by_refs = filter_by_refs)
context.changed_refs = reffilters
context.output = options.output
impls.output = options.output
for phase in impls.INVOCATION_PHASES:
log.info("Generating phase '%s'" % phase)
context.phase = phase
impls.generate(context)
impls.post_generate(context)
if options.what:
log.info("Write output files to '%s'" % options.what)
output_files = []
for op in context.get_output():
# Only append once
if op.type == 'file' and output_files.count(op.abspath) < 1:
output_files.append(op.abspath)
try:
mkpath(os.path.dirname(os.path.abspath(options.what)))
what_fh = open(os.path.abspath(options.what), 'w')
try:
[what_fh.write('%s\n' % ofile) for ofile in output_files]
print "Wrote output file list to '%s'" % options.what
finally:
what_fh.close()
except Exception:
log.info("Could not create directory for '%s'" % options.what)
print "Generated %s to %s!" % (options.configuration, impls.output)
# Store temporary rule execution outputs to a new configuration
if options.dump_autodata:
# Make sure autodata layer is the one we're dealing with
plugin.get_autoconfig(config)
lastconfig = config.get_last_configuration()
lastconfig.set_name(utils.resourceref.to_objref(utils.resourceref.get_filename(utils.resourceref.norm(options.dump_autodata))))
data = persistentconfml.dumps(lastconfig)
try:
mkpath(utils.resourceref.get_path(utils.resourceref.norm(options.dump_autodata)))
fh = open(options.dump_autodata, 'w')
try: fh.write(data)
finally: fh.close()
print 'Saved autodata to %s' % options.dump_autodata
except DistutilsFileError:
log.info('Unable to dump autodata')
# ---------------
# Generate report
# ---------------
# If reporting is enabled collect data for report
if options.report != None or options.report_data_output != None:
logging.getLogger('cone').info('Collecting data for report.')
rep_data = generation_report.ReportData()
rep_data.context = context
rep_data.context.log_file = os.path.abspath(options.log_file)
rep_data.context.log = _read_log(options.log_file)
rep_data.project_dir = options.project
logging.getLogger('cone').info('Collecting data found rep_data %s' % rep_data)
duration = str("%.3f" % (time.time() - start_time) )
rep_data.set_duration( duration )
rep_data.options = options
# Save intermediary report data file if necessary
if options.report_data_output != None:
logging.getLogger('cone').info('Dumping report data to %s' % options.report_data_output)
print "Dumping report data to '%s'" % options.report_data_output
generation_report.save_report_data(rep_data, options.report_data_output)
# Generate the report if necessary
if options.report != None:
generation_report.generate_report([rep_data], options.report, options.template, [ROOT_PATH], options.report_option)
print_summary(rep_data)
if current: current.close()
def _read_log(log_file):
logf = open(log_file)
# strip endlines
return [line.strip('\n') for line in logf.readlines()]
def _get_new_refs(old_refs, new_refs):
"""
Return a distinct array of refs in ``new_refs`` that are not present in ``old_refs``.
"""
result = []
for ref in new_refs:
if ref not in old_refs and ref not in result:
result.append(ref)
return result
def _filter_by_refs(config, options, parser):
"""
"""
filter_by_refs = True
if options.all_layers:
filter_by_refs = False
elif not options.layers and not options.layer_regexes and not options.layer_wildcards:
filter_by_refs = False
return filter_by_refs
def _get_included_layers(config, options, parser):
"""
Collect a list of included layer root paths from the config based on the
given parameters in options.
@return: A list of layer configuration paths (empty if all layers
should be generated).
"""
# --all-layers overrides all other definitions
if options.all_layers:
options.layers = [i for i in range(len(config.list_configurations()))]
elif not options.layers and not options.layer_regexes and not options.layer_wildcards:
options.layers = [i for i in range(len(config.list_configurations()))]
# Command line definitions override others
if options.layers or options.layer_regexes or options.layer_wildcards:
layer_paths = []
all_layers = config.list_configurations()
for layer_index in options.layers or []:
try:
layer_paths.append(all_layers[int(layer_index)])
except (IndexError, ValueError):
parser.error("Invalid layer index: %s" % layer_index)
for regex in options.layer_regexes or []:
for layer_path in all_layers:
if re.search(regex, layer_path):
layer_paths.append(layer_path)
for wildcard in options.layer_wildcards or []:
for layer_path in all_layers:
if fnmatch.fnmatch(layer_path, wildcard):
layer_paths.append(layer_path)
if not layer_paths:
parser.error('No layers matched by layer patterns')
return utils.distinct_array(layer_paths)
# Use iMaker API definitions if no others have been specified
return _get_included_layers_from_imaker_api(config, parser)
def _get_included_layers_from_imaker_api(config, parser):
try:
layer_str_list = (config.get_default_view().get_feature('imakerapi.cone_layers').get_value() or '').split(',')
# Make sure that empty layers definitions are ignored
layer_str_list = utils.distinct_array(layer_str_list)
if '' in layer_str_list:
layer_str_list.remove('')
all_layers = config.list_configurations()
layerdefs = []
for layerstr in layer_str_list:
try:
layerdefs.append(all_layers[int(layerstr)])
except (ValueError, IndexError):
parser.error("Invalid layer index from iMaker API: %s" % layerstr)
return layerdefs
except exceptions.NotFound:
return []
def print_summary(rep_data):
""" Prints generation summary to logger and console. """
print "\nGENERATION SUMMARY:"
print "--------------------"
print "Refs in files: %s" % len(rep_data.context.changed_refs)
print "Refs with no implementation: %s" % len(rep_data.context.get_refs_with_no_output())
print "Generation duration: %s" % rep_data.duration
print "\n\n"
if __name__ == "__main__":
main()