#
# Copyright (c) 2007-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: 
#

# Python Script to create the vmap file for Binary Variation support in SBSv2

import sys
import os
import re
import subprocess
import tempfile
import traceback
from optparse import OptionParser

# the script will exit with 0 if there are no errors
global exitCode
exitCode = 0

# are we running on Windows?
onWindows = sys.platform.lower().startswith("win")

# error messages go to stderr
def error(format, *extras):
	sys.stderr.write("createvmap: error: " + (format % extras) + "\n")
	global exitCode
	exitCode = 1

# warning messages go to stderr
def warning(format, *extras):
	sys.stderr.write("createvmap: warning: " + (format % extras) + "\n")

# debug messages go to stderr
global printDebug
#
def debug(format, *extras):
	if printDebug:
		sys.stderr.write("createvmap: " + (format % extras) + "\n")
	
# Return a dictionary with the feature names and values from the preinclude file, by running cpp over the source
def getVmapMacros(aPreInclude, aPreprocessedFile=None, aCPP="cpp", aDefines="", aIncludes = ""):

	validmacros = {}
	# Run the pre-processor
	command = aCPP + " -include " + os.path.abspath(aPreInclude) + " -dU " + aDefines + aIncludes

	# Feed in the file to stdin, because we must set the stdin to something
	# other than the parent stdin anyway as that may not exist - for example
	# when using Talon.
	infile = open(aPreprocessedFile, "r")

	if onWindows:
		p = subprocess.Popen(command, bufsize=65535,
					                  stdin=infile,
					                  stdout=subprocess.PIPE,
					                  stderr=sys.stderr,
					                  universal_newlines=True)
	else:
		p = subprocess.Popen(command, bufsize=65535,
					                  stdin=infile,
					                  stdout=subprocess.PIPE,
					                  stderr=sys.stderr,
					                  close_fds=True, shell=True)
	stream = p.stdout

	# Parse the pre-processor output to look for -
	# lines "#define NAME VALUE" and "#undef NAME"
	defineRE = re.compile('^#define (?P<FEATURENAME>\w+)(\s+(?P<VALUE>\w+))?')
	undefRE = re.compile('^#undef (?P<FEATURENAME>\w+)')

	data = " "
	while data:
		data = stream.readline()

		definedmacro = defineRE.match(data)
		if definedmacro:
			name = definedmacro.group('FEATURENAME')
			value = definedmacro.group('VALUE')
			if value:
				validmacros[name] = value
			else:
				validmacros[name] = "defined"

		else:
			undefinedmacro = undefRE.match(data)
			if undefinedmacro:
				validmacros[undefinedmacro.group('FEATURENAME')] = "undefined"

	if p.wait() != 0:
		error("in command '%s'", command)
		
	infile.close()
	
	return validmacros

# Extract the features from a featurelist file
def getFeatures(aFeatureList):
	features = set()
	for f in aFeatureList:
		try:
			file = open(os.path.abspath(f),'r')
		
			for data in file.readlines():
				data = data.strip()
				features.add(data)
		
			file.close()
		
		except IOError:
			error("Feature list file %s not found", f)

	return sorted(list(features))
	
# Returns a dictionary of the features to be put in the vmap file
def getVariationFeatures(aFeatureList = [] ,aPreinclude = None,aPreprocessedFile = None,aCPP = "cpp",aDefines="",aIncludes = ""):
	
	variation_features = {'FEATURENAME':[],'VALUE':[]}
	macros = getVmapMacros(aPreinclude,aPreprocessedFile,aCPP,aDefines,aIncludes)
	
	# Co-relate the macros obtained from the pre-processor to the featurelist
	for f in aFeatureList:
		if f in macros:
			variation_features['FEATURENAME'].append(f)
			variation_features['VALUE'].append(macros[f])
	
	return variation_features

# Write to the vmap file, with the supplied dictionary containing the features
# The vmap path will be created if it doesn't exist
def createVmapFile(aMacroDictionary,aOutputfile):
	if not os.path.exists(os.path.dirname(aOutputfile)):
		os.makedirs(os.path.dirname(aOutputfile))
	try:
		vmapfile = open(aOutputfile,'w')
	except IOError:
		error("Cannot write to " + aOutputfile)
	i = 0
	while i < len(aMacroDictionary['FEATURENAME']):
		vmapfile.write(aMacroDictionary['FEATURENAME'][i]+"="+aMacroDictionary['VALUE'][i]+"\n")
		i += 1
	vmapfile.close()

def check_exists(thing, filenames):
	if not filenames:
		error("No %s specified", thing)
		return
	
	if not isinstance(filenames, list):
		# we just have a single string
		filenames = [filenames]
		
	for filename in filenames:
		if not os.path.exists(filename):
			error("The %s '%s' does not exist", thing, filename)
		
# Main function, creates the vmap file
def main():

	try:
		global exitCode, printDebug
		
		# any exceptions make us traceback and exit

		parser = OptionParser(prog = "createvmap.py")
	
		parser.add_option("-c","--cpploc",action="store",dest="cpplocation",help="Full path of the preprocessor")
		parser.add_option("-d","--debug",action="store_true",default=False,dest="debug",help="Turn debug information on")
		parser.add_option("-D","--define",action="append",dest="defines",help="Macro definition")
		parser.add_option("-f","--featurelist",action="append",dest="featurelistfile",help="List of featureslist files")
		parser.add_option("-o","--output",action="store",dest="outputvmapfile",help="Output VMAP file name")
		parser.add_option("-p","--preinclude",action="store",dest="preinclude",help="Pre-include file ")
		parser.add_option("-s","--source",action="append",dest="sourcefiles",help="List of source files")
		parser.add_option("-u","--userinc",action="append",dest="user_include",help="User Include Folders")
		parser.add_option("-x","--systeminc",action="append",dest="system_include",help="System Include Folders")

		(options, leftover_args) = parser.parse_args(sys.argv[1:])

		if leftover_args:
			for invalids in leftover_args:
				warning("Unknown parameter '%s'" % invalids)
		
		printDebug = options.debug
		debug("Source Files     -> %s", options.sourcefiles)
		debug("Macro defines    -> %s", options.defines)
		debug("Features Files   -> %s", options.featurelistfile)
		debug("Pre-Include File -> %s", options.preinclude)
		debug("User Includes    -> %s", options.user_include)
		debug("System Includes  -> %s", options.system_include)
		debug("CPP Location     -> %s", options.cpplocation)
		debug("VMAP Output name -> %s", options.outputvmapfile)
			
		featurelist = []
		definelist = ""
		user_includeslist = ""
		system_includeslist = ""
		includeslist = ""

		# Some error checking code
		if not options.outputvmapfile:
			error("No output vmap file name supplied")
	
		# Source files must be supplied
		check_exists("source file", options.sourcefiles)
	
		# A valid preinclude file must be supplied
		check_exists("pre-include file", options.preinclude)
	
		# Some feature lists are required
		check_exists("feature list", options.featurelistfile)
	
		# A cpp tool is required
		check_exists("cpp tool", options.cpplocation)

		# if an essential option was missing then we should stop now
		if exitCode != 0:
			sys.exit(exitCode)
			
		# macro definitions
		if options.defines:
			for macro in options.defines:
				definelist += " -D" + macro.replace('__SBS__QUOTE__', '\\"')

		# Note that we have to use -isystem for user includes and system
		# includes to match what happens in the compiler. Long story.

		# Add each source directory as a user-include, so that our temporary
		# concatenated source file can find includes that were next to the
		# original source files.
		# Check that all the specified source files exist
		# and collect a set of all the source directories
		sourcedirs = set()
		for src in options.sourcefiles:
			sourcedirs.add(os.path.dirname(src))
			
		for srcdir in sourcedirs:
			user_includeslist += " -isystem " + srcdir

		# Convert the include list to a string to be passed to cpp
		if options.user_include:
			for userinc in options.user_include:
				user_includeslist += " -isystem " + userinc
		if options.system_include:
			for sysinc in options.system_include:
				system_includeslist += " -isystem " + sysinc
	
		includeslist = user_includeslist + system_includeslist

		# Get a list of all the features, from all the featurelist files
		featurelist = getFeatures(options.featurelistfile)

		# concatenate the source files together into a temporary file
		try:
			(tempfd, tempname) = tempfile.mkstemp()
			temp = os.fdopen(tempfd, "w")
			for src in options.sourcefiles:
				sfile = open(src, "r")
				for sline in sfile:
					temp.write(sline)
				sfile.close()
			temp.close()
		except Exception,e:
			error("Could not write source files into temporary file %s : %s" % (tempname, str(e)))
			return 1
		
		debug("Temporary file name : " + tempname)

		# extract the macros from the concatenated source files
		macro_dictionary = getVariationFeatures(featurelist,
		                                        options.preinclude,
								                tempname,
								                options.cpplocation,
								                definelist,
								                includeslist)
		debug("Macros extracted:") 
		for key,values in macro_dictionary.iteritems():
			debug(key + " " + str(values))

		# if there were no macros then the vmap file will be empty...
		if not macro_dictionary['FEATURENAME']:
			warning("No feature macros were found in the source")
			
		# Get rid of the temporary file
		try:
			os.remove(tempname)
		except:
			error("Could not delete temporary %s" % tempname) 

		createVmapFile(macro_dictionary, options.outputvmapfile)
		
		# exit with 0 if OK
		return exitCode

	except Exception,ex:
		traceback.print_exc()
		return 1

if __name__ == "__main__":
    sys.exit(main())

