#
# Copyright (c) 2006-2009 Nokia Corporation and/or its subsidiary(-ies).
# All rights reserved.
# This component and the accompanying materials are made available
# under the terms of the License "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: 
# raptor_cli module
# This module represents a Command Line Interpreter (CLI) for Raptor.
# The interface with Raptor is the GetArgs() function, which is called
# by a raptor.Raptor object.
#

import re
import types
import raptor
import os
import sys
import tempfile
from raptor_utilities import getOSPlatform

from optparse import OptionParser # for parsing command line parameters

fullCommandOption = "--command"
miniCommandOption = "--co"  # update this if another "co" option is added

# raptor_cli module attributes

parser = OptionParser(prog = raptor.name,
					  usage = """%prog [--help] [options] [variable=value] [target] ...

Targets:

BITMAP         Create bitmap files
CLEAN          Remove built files and intermediates, but not exported files
CLEANEXPORT    Remove exported files
EXPORT         Copy exported files to destinations
FINAL          Allow extension makefiles to execute final commands
FREEZE         Freeze exported functions in a .DEF file
LIBRARY        Create import libraries from frozen .DEF files
LISTING        Create assembler listing files for source files
REALLYCLEAN    Same as CLEAN but also remove exported files
RESOURCE       Create resource files and AIFs
ROMFILE        Create an IBY file to be included in a ROM
TARGET         Create main executables
WHAT           List all releaseable targets

Examples:

sbs -b my/group/bld.inf -c armv5        # build my component for target ARMV5
sbs -b my/group/bld.inf -c armv5.test   # build my tests for target ARMV5

sbs -c winscw CLEAN                     # clean emulator files
sbs REALLYCLEAN                         # delete everything""")

parser.add_option("-a","--sysdefbase",action="store",dest="sys_def_base",
				help="Root directory for relative paths in the System Definition XML file.")

parser.add_option("-b","--bldinf",action="append",dest="bld_inf_file",
				help="Build information filename. Multiple -b options can be given.")

parser.add_option("-c","--config",action="append",dest="config_name",
				help="Configuration name to build. Multiple -c options can be given. The standard configs are all, armv5, armv7, default, tools, tools2 and winscw.")

parser.add_option("--configpath", action="append",dest="config_list",
				help="Append a list of paths to the default list of XML configuration folders. Use ';' as the separator on Windows, and ':' on Linux. Multiple --configpath options can be given.")

parser.add_option("--check",action="store_true",dest="check",
				help="Test for the existence of files created by the build, printing the ones which are missing. Do not build anything.")

parser.add_option("--command",action="append",dest="command_file",
				help="Provide a set of command-line options in a file.")

parser.add_option("-d","--debug",action="store_true",dest="debugoutput",
				help="Display information useful for debugging.")

parser.add_option("-e","--engine",action="store",dest="make_engine",
				help="Name of the make engine which runs the build.")

parser.add_option("--export-only",action="store_true",dest="doExportOnly",
				help="Generate exports only and do not create any make files.")

parser.add_option("-f","--logfile",action="store",dest="logfile",
				help="Name of the log file, or '-' for stdout.")

parser.add_option("--filters",action="store",dest="filter_list",
				help="Comma-separated list of names of the filters to use (case sensitive).")

parser.add_option("-i","--ignore-os-detection",action="store_true",dest="ignore_os_detection",
				help="Disables automatic application of OS variant based upon the OS version detected from each epoc32 tree.")

parser.add_option("-j","--jobs",action="store",dest="number_of_jobs",
                help="The maximum number of jobs that make should try and run in parallel (on a single machine).")

parser.add_option("-k","--keepgoing",action="store_true",dest="keepgoing",
				help="Continue building, even if some build commands fail.")

parser.add_option("-l","--layer",action="append",dest="sys_def_layer",
                help="Build a specific layer in the System Definition XML File. Multiple -l options can be given.")

parser.add_option("-m","--makefile",action="store",dest="makefile",
				help="Top-level makefile to be created.")

parser.add_option("--mo",action="append",dest="make_option",
				help="Option that must be passed through to the make engine. Multiple --mo options can be given.")

parser.add_option("-n","--nobuild",action="store_true",dest="nobuild",
				help="Just create makefiles, do not build anything.")

parser.add_option("--no-depend-include",action="store_true",dest="noDependInclude",
				help="Do not include generated dependency files. This is only useful for extremely large non-incremental builds.")
				
parser.add_option("-o","--orderlayers",action="store_true",dest="sys_def_order_layers",
				help="Build layers in the System Definition XML file in the order listed or, if given, in the order of -l options.")

parser.add_option("-p","--project",action="append",dest="project_name",
                help="Build a specific project (mmp or extension) in the given bld.inf file. Multiple -p options can be given.")

parser.add_option("-q","--quiet",action="store_true",dest="quiet",
				help="Run quietly, not generating output messages.")

parser.add_option("-s","--sysdef",action="store",dest="sys_def_file",
				help="System Definition XML filename.")

parser.add_option("--source-target",action="append",dest="source_target",
				help="Build the listed source or resource file in isolation - do not perform any dependent processing. Multiple --source-target options can be given.")

parser.add_option("-t","--tries",action="store",dest="tries",
				help="How many times to run a command before recording an error. The default is 1. This is useful for builds where transient failures can occur.")

parser.add_option("--toolcheck",action="store",dest="toolcheck",
			help= \
				"""Possible values are:
				  "on"     -  Check the versions of tools that will be used in the build. Use cached results from previous builds to save time. This is the default.

  				  "off"    -  Do not check tool versions whatsoever.

				  "forced" -  Check all tool versions. Don't use cached results.
			""")
parser.add_option("--pp",action="store",dest="parallel_parsing",
				help="""Controls how metadata (e.g. bld.infs) are parsed in Parallel.
					Possible values are:
					"on"  - Parse bld.infs in parallel (should be faster on clusters/multicore machines)
					"off" - Parse bld.infs serially 
				     """)

parser.add_option("-v","--version",action="store_true",dest="version",
				help="Print the version number and exit.")

parser.add_option("--what",action="store_true",dest="what",
				help="Print out the names of the files created by the build. Do not build anything.")

def GetArgs(Raptor, args):
	"Process command line arguments for a Raptor object"
	return DoRaptor(Raptor,args)

def ReadCommandFile(filename, used):
	if filename in used:
		raise IOError("command file '%s' refers to itself" % filename)

	args = []
	try:
		file = open(filename, "r")
		for line in file.readlines():
			args.extend(line.split())
		file.close()
	except:
		raise IOError("couldn't read command file '%s'" % filename)

	# expand any command files in the options we just read.
	# making sure we don't get stuck in a loop.
	usedPlusThis = used[:]
	usedPlusThis.append(filename)
	return ExpandCommandOptions(args, usedPlusThis)

def ExpandCommandOptions(args, files = []):
	"""recursively expand --command options."""
	expanded = []
	previousWasOpt = False

	for a in args:
		if previousWasOpt: # then this one is the filename
			expanded.extend(ReadCommandFile(a, files))
			previousWasOpt = False
			continue

		if a.startswith(miniCommandOption):
			if "=" in a: # then this is opt=filename
				opt = a.split("=")
				if fullCommandOption.startswith(opt[0]):
					expanded.extend(ReadCommandFile(opt[1], files))
					continue
			else: # the next one is the filename
				if fullCommandOption.startswith(a):
					previousWasOpt = True
					continue

		expanded.append(a) # an ordinary arg, nothing to do with command files

	return expanded

def DoRaptor(Raptor, args):
	"Process raptor arguments"
	#
	# This should parse the args list and call methods on
	# the Raptor object to store the appropriate data.

	# Expand --command=file options, replacing them with the contents of the
	# command file.

	non_ascii_error = "Non-ASCII character in argument or command file"

	try:
		expanded_args = ExpandCommandOptions(args)
		for arg in expanded_args:
			for c in arg:
				if ord(c) > 127:
					Raptor.Error(non_ascii_error)
					return False
	except IOError, e:
		Raptor.Error(str(e))
		return False
	except UnicodeDecodeError:
		Raptor.Error(non_ascii_error)
		return False

	# parse the full set of arguments
	(options, leftover_args) = parser.parse_args(expanded_args)

	# the leftover_args are either variable assignments of the form a=b
	# or target names.
	regex = re.compile("^(.+)=(.*)$")
	for leftover in leftover_args:
		assignment = regex.findall(leftover)
		if len(assignment) > 0:
			Raptor.SetEnv(assignment[0][0],assignment[0][1])
		else:
			Raptor.AddTarget(leftover)

	# Define the dictionary of functions to be used.
	# Attributes and function names can be added easily.
	# The calling attribute should be the same
	# as specified when creating the add_option
	functions = {'config_name': Raptor.AddConfigName,
				 'config_list':Raptor.AddConfigList,
				 'sys_def_file' : Raptor.SetSysDefFile,
				 'sys_def_base' : Raptor.SetSysDefBase,
				 'sys_def_layer' : Raptor.AddSysDefLayer,
				 'sys_def_order_layers' : Raptor.SetSysDefOrderLayers,
				 'bld_inf_file' : Raptor.AddBuildInfoFile,
				 'logfile' : Raptor.SetLogFileName,
				 'makefile' : Raptor.SetTopMakefile,
				 'quiet' : Raptor.RunQuietly,
				 'debugoutput' : Raptor.SetDebugOutput,
				 'doExportOnly' : Raptor.SetExportOnly,
				 'keepgoing': Raptor.SetKeepGoing,
				 'nobuild' : Raptor.SetNoBuild,
				 'make_engine': Raptor.SetMakeEngine,
				 'make_option': Raptor.AddMakeOption,
				 'noDependInclude': Raptor.SetNoDependInclude,
				 'number_of_jobs': Raptor.SetJobs,
				 'project_name' :  Raptor.AddProject,
				 'filter_list' : Raptor.FilterList,
				 'ignore_os_detection': Raptor.IgnoreOsDetection,
				 'check' :  Raptor.SetCheck,
				 'what' :  Raptor.SetWhat,
				 'tries' : Raptor.SetTries,
				 'toolcheck' : Raptor.SetToolCheck,
				 'source_target' : Raptor.AddSourceTarget,
				 'command_file' : CommandFile,
				'parallel_parsing' : Raptor.SetParallelParsing,
			 	'version' : Raptor.PrintVersion
				}

	# Check if Quiet mode has been specified (otherwise we will make noise)
	if parser.values.quiet:
		Raptor.RunQuietly(True)

	# some options imply that Raptor should exit immediately (e.g. --version)
	keepGoing = True

	if parser.values.version:
		keepGoing = False

	# Parse through the command line arguments passed, and call the
	# corresponding function with the correct parameter.
	# Since options is a OptParse.Value instance, it can be iterated over.
	# This implementation helps avoid lengthy if-else statements
	for opt in options.__dict__.items():
		call_function = functions[str(opt[0])]
		values = opt[1]
		if not values:
			pass
		else:
			if type(values) == types.ListType: # Check if the argument type is a list or a string. If list, then iterate through it and call the functions
				for val in values:
					keepGoing = (call_function(val) and keepGoing)
			else:
					keepGoing = (call_function(values) and keepGoing)

	return keepGoing

def CommandFile(file):
	"this should never be called because we expand --command in this module."
	print raptor.name + ": error: command file '%s' was not expanded" % file
	return False




# end of the raptor_cli module


