sbsv2/raptor/python/raptor_data.py
changeset 0 044383f39525
child 3 e1eecf4d390d
child 590 360bd6b35136
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sbsv2/raptor/python/raptor_data.py	Tue Oct 27 16:36:35 2009 +0000
@@ -0,0 +1,1489 @@
+#
+# 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_data module
+# This module contains the classes that make up the Raptor Data Model.
+#
+
+import copy
+import generic_path
+import os
+import hashlib
+import raptor_utilities
+import re
+import types
+import sys
+import subprocess
+from tempfile import gettempdir
+from time import time, clock
+
+
+
+# What host platforms we recognise
+# This allows us to tie some variants to one host platform and some to another
+class HostPlatform(object):
+	""" List the host platforms on which we can build.  Allow configuration
+ 	    files to specify different information based on that.
+	"""
+	hostplatforms = ["win32", "win64", "linux2"]
+	hostplatform = sys.platform
+
+	@classmethod
+	def IsKnown(cls, platformpattern):
+		"Does the parameter match the name of a known platform "
+		hpnre = re.compile(platformpattern, re.I)
+		for hp in cls.hostplatforms:
+			if hpnre.match(hp):
+				return True
+		return False
+
+	@classmethod
+	def IsHost(cls, platformpattern):
+		""" Does the parameter match the name of the
+		    platform that we're executing on? """
+		ppre = re.compile(platformpattern, re.I)
+		if ppre.match(cls.hostplatform):
+			return True
+		return False
+
+
+# Make sure not to start up on an unsupported platform
+if not HostPlatform.IsKnown(HostPlatform.hostplatform):
+	raise Exception("raptor_data module loaded on an unrecognised platform '%s'. Expected one of %s" % (hostplatform, str(hostplatforms)))
+
+
+# raptor_data module classes
+
+class Model(object):
+	"Base class for data-model objects"
+
+	def __init__(self):
+		self.owner = None	# Raptor object
+		self.source = None	# XML file
+		self.indent = " "	# for DebugPrint
+		self.host = None
+		self.cacheID = ""	# default set of cached objects
+
+
+	def SetOwner(self, aRaptor):
+		self.owner = aRaptor
+
+
+	def SetSourceFile(self, filename):
+		self.source = filename
+
+
+	def SetProperty(self, name, value):
+		raise InvalidPropertyError()
+
+
+	def AddChild(self, child):
+		raise InvalidChildError()
+
+
+	def DebugPrint(self, prefix = ""):
+		if self.owner:
+			self.owner.Debug("%s", prefix)
+
+	def Valid(self):
+		return False
+
+	def IsApplicable(self):
+		"This variant may be caused to only apply when used on a particular host build platform"
+		if self.host is None:
+			return True
+
+		if HostPlatform.IsHost(self.host):
+			return True
+
+		return False
+
+
+class InvalidPropertyError(Exception):
+	pass
+
+class InvalidChildError(Exception):
+	pass
+
+class BadReferenceError(Exception):
+	pass
+
+
+class Reference(Model):
+	"Base class for data-model reference objects"
+
+	def __init__(self, ref = None):
+		Model.__init__(self)
+		self.ref = ref
+		self.modifiers = []
+
+	def SetProperty(self, name, value):
+		if name == "ref":
+			self.ref = value
+		elif name == "mod":
+			self.modifiers = value.split(".")
+		else:
+			raise InvalidPropertyError()
+
+	def Resolve(self):
+		raise BadReferenceError()
+
+	def GetModifiers(self):
+		cache = self.owner.cache
+		return [ cache.FindNamedVariant(m) for m in self.modifiers ]
+
+	def Valid(self):
+		return self.ref
+
+
+class VariantContainer(Model):
+
+	def __init__(self):
+		Model.__init__(self)	# base class constructor
+		self.variants = []
+
+
+	def SetOwner(self, aRaptor):
+		Model.SetOwner(self, aRaptor)
+		for v in self.variants:
+			v.SetOwner(aRaptor)
+
+
+	def DebugPrint(self, prefix = ""):
+		for v in self.variants:
+			v.DebugPrint(prefix)
+
+
+	def AddVariant(self, variant):
+		if type(variant) is types.StringTypes:
+			variant = VariantRef(variant)
+
+
+		# Only add the variant if it's not in the list
+		# already
+		if not variant in self.variants:
+			self.variants.append(variant)
+
+	def GetVariants(self):
+		# resolve any VariantRef objects into Variant objects
+		for i,var in enumerate(self.variants):
+			if isinstance(var, Reference):
+				try:
+					self.variants[i] = var.Resolve()
+
+				except BadReferenceError:
+					self.owner.Error("Missing variant '%s'", var.ref)
+
+		return self.variants
+
+
+class Interface(Model):
+
+	def __init__(self, name = None):
+		Model.__init__(self)	# base class constructor
+		self.name = name
+		self.flm = None
+		self.abstract = False
+		self.extends = None
+		self.params = []
+		self.paramgroups = []
+
+	def DebugPrint(self, prefix = ""):
+		self.owner.Debug("%s<interface name='%s'>", prefix, self.name)
+		self.owner.Debug("%s...", prefix)
+		self.owner.Debug("%s</interface>", prefix)
+
+	def FindParent(self):
+		try:
+			return self.owner.cache.FindNamedInterface(self.extends, self.cacheID)
+		except KeyError:
+			raise BadReferenceError("Cannot extend interface because it cannot be found: "+str(self.extends))
+
+	def GetParams(self):
+		if self.extends != None:
+			parent = self.FindParent()
+
+			# what parameter names do we have already?
+			names = set([x.name for x in self.params])
+
+			# pick up ones we don't have that are in our parent
+			pp = []
+			for p in parent.GetParams():
+				if not p.name in names:
+					pp.append(p)
+
+			# list parent parameters first then ours
+			pp.extend(self.params)
+			return pp
+
+		return self.params
+
+	def GetParamGroups(self):
+		if self.extends != None:
+			parent = self.FindParent()
+
+			# what parameter names do we have already?
+			patterns = set([x.pattern for x in self.paramgroups])
+
+			# pick up ones we don't have that are in our parent
+			for g in parent.GetParamGroups():
+				if not g.pattern in patterns:
+					self.paramgroups.append(g)
+
+		return self.paramgroups
+
+
+	def GetFLMIncludePath(self):
+		"absolute path to the FLM"
+
+		if self.flm == None:
+			if self.extends != None:
+				parent = self.FindParent()
+
+				return parent.GetFLMIncludePath()
+			else:
+				raise InvalidPropertyError()
+
+		if not os.path.isabs(self.flm):
+			self.flm = os.path.join(os.path.dirname(self.source), self.flm)
+
+		return generic_path.Path(self.flm)
+
+
+	def SetProperty(self, name, value):
+		if name == "name":
+			self.name = value
+		elif name == "flm":
+			self.flm = value
+		elif name == "abstract":
+			self.abstract = (value == "true")
+		elif name == "extends":
+			self.extends = value
+		else:
+			raise InvalidPropertyError()
+
+
+	def AddChild(self, child):
+		if isinstance(child, Parameter):
+			self.AddParameter(child)
+		elif isinstance(child, ParameterGroup):
+			self.AddParameterGroup(child)
+		else:
+			raise InvalidChildError()
+
+
+	def AddParameter(self, parameter):
+		self.params.append(parameter)
+
+	def AddParameterGroup(self, parametergroup):
+		self.paramgroups.append(parametergroup)
+
+	def Valid(self):
+		return (self.name != None)
+
+
+class InterfaceRef(Reference):
+
+	def DebugPrint(self, prefix = ""):
+		self.owner.Debug("%s<interfaceRef ref='%s'/>", prefix, self.ref)
+
+	def Resolve(self):
+		try:
+			return self.owner.cache.FindNamedInterface(self.ref, self.cacheID)
+		except KeyError:
+			raise BadReferenceError()
+
+
+class Specification(VariantContainer):
+
+	def __init__(self, name = "", type = ""):
+		VariantContainer.__init__(self)	# base class constructor
+		self.name = name
+		self.type = type
+		self.interface = None
+		self.childSpecs = []
+		self.parentSpec = None
+
+
+	def DebugPrint(self, prefix = ""):
+		self.owner.Debug("%s<spec name='%s'>", prefix, self.name)
+		if self.interface:
+			self.interface.DebugPrint(prefix + self.indent)
+		VariantContainer.DebugPrint(self, prefix + self.indent)
+		for c in self.childSpecs:
+			c.DebugPrint(prefix + self.indent)
+		self.owner.Debug("%s</spec>", prefix)
+
+
+	def SetOwner(self, aRaptor):
+		VariantContainer.SetOwner(self, aRaptor)
+
+		if self.interface != None:
+			self.interface.SetOwner(aRaptor)
+
+		for s in self.childSpecs:
+			s.SetOwner(aRaptor)
+
+
+	def SetProperty(self, name, value):
+		if name == "name":
+			self.name = value
+		else:
+			raise InvalidPropertyError()
+
+
+	def Configure(self, config):
+		# configure all the children (some may be Filters or parents of)
+		for spec in self.GetChildSpecs():
+			spec.Configure(config)
+
+
+	def HasInterface(self):
+		return self.interface != None
+
+
+	def SetInterface(self, interface):
+		if isinstance(interface, Interface) \
+		or isinstance(interface, InterfaceRef):
+			self.interface = interface
+		else:
+			self.interface = InterfaceRef(interface)
+
+
+	def GetInterface(self):
+		"""return the Interface (fetching from the cache if it was a ref)
+		may return None"""
+
+		if self.interface == None \
+		or isinstance(self.interface, Interface):
+			return self.interface
+
+		if isinstance(self.interface, InterfaceRef):
+			try:
+				self.interface = self.interface.Resolve()
+				return self.interface
+
+			except BadReferenceError:
+				self.owner.Error("Missing interface %s", self.interface.ref)
+				return None
+
+
+	def AddChild(self, child):
+		if isinstance(child, Specification):
+			self.AddChildSpecification(child)
+		elif isinstance(child, Interface) \
+		  or isinstance(child, InterfaceRef):
+			self.SetInterface(child)
+		elif isinstance(child, Variant) \
+		  or isinstance(child, VariantRef):
+			self.AddVariant(child)
+		else:
+			raise InvalidChildError()
+
+
+	def AddChildSpecification(self, child):
+		child.SetParentSpec(self)
+		self.childSpecs.append(child)
+
+
+	def SetParentSpec(self, parent):
+		self.parentSpec = parent
+
+
+	def GetChildSpecs(self):
+		return self.childSpecs
+
+
+	def Valid(self):
+		return True
+
+
+	def GetAllVariantsRecursively(self):
+		"""Returns all variants contained in this node and in its ancestors.
+
+		The returned value is a list, the structure of which is [variants-in-parent,
+		variants-in-self].
+
+		Note that the function recurses through parent *Specifications*, not through
+		the variants themselves.
+		"""
+		if self.parentSpec:
+			variants = self.parentSpec.GetAllVariantsRecursively()
+		else:
+			variants = []
+
+		variants.extend( self.GetVariants() )
+
+		return variants
+
+
+class Filter(Specification):
+	"""A Filter is two Specification nodes and a True/False switch.
+
+	Filter extends Specification to have two nodes, only one of
+	which can be active at any time. Which node is active is determined
+	when the Configure method is called after setting up a Condition.
+
+	If several Conditions are set, the test is an OR of all of them."""
+
+	def __init__(self, name = ""):
+		Specification.__init__(self, name)	# base class constructor
+		self.Else = Specification(name)     # same for Else part
+		self.isTrue = True
+		self.configNames = set()            #
+		self.variableNames = set()          # TO DO: Condition class
+		self.variableValues = {}            #
+
+	def DebugPrint(self, prefix = ""):
+		self.owner.Debug("%s<filter name='%s'>", prefix, self.name)
+		self.owner.Debug("%s <if config='%s'>", prefix, " | ".join(self.configNames))
+		Specification.DebugPrint(self, prefix + self.indent)
+		self.owner.Debug("%s </if>", prefix)
+		self.owner.Debug("%s <else>", prefix)
+		self.Else.DebugPrint(prefix + self.indent)
+		self.owner.Debug("%s </else>", prefix)
+		self.owner.Debug("%s</filter>", prefix)
+
+
+	def SetConfigCondition(self, configName):
+		self.configNames = set([configName])
+
+	def AddConfigCondition(self, configName):
+		self.configNames.add(configName)
+
+
+	def SetVariableCondition(self, variableName, variableValues):
+		self.variableNames = set([variableName])
+		if type(variableValues) == types.ListType:
+			self.variableValues[variableName] = set(variableValues)
+		else:
+			self.variableValues[variableName] = set([variableValues])
+
+	def AddVariableCondition(self, variableName, variableValues):
+		self.variableNames.add(variableName)
+		if type(variableValues) == types.ListType:
+			self.variableValues[variableName] = set(variableValues)
+		else:
+			self.variableValues[variableName] = set([variableValues])
+
+
+	def Configure(self, buildUnit):
+		self.isTrue = False
+
+		if buildUnit.name in self.configNames:
+			self.isTrue = True
+		elif self.variableNames:
+			evaluator = self.owner.GetEvaluator(self.parentSpec, buildUnit)
+
+			for variableName in self.variableNames:
+				variableValue = evaluator.Get(variableName)
+
+				if variableValue in self.variableValues[variableName]:
+					self.isTrue = True
+					break
+
+		# configure all the children too
+		for spec in self.GetChildSpecs():
+			spec.Configure(buildUnit)
+
+
+	def SetOwner(self, aRaptor):
+		# base class method
+		Specification.SetOwner(self, aRaptor)
+		# same for Else part
+		self.Else.SetOwner(aRaptor)
+
+
+	def HasInterface(self):
+		if self.isTrue:
+			return Specification.HasInterface(self)
+		else:
+			return self.Else.HasInterface()
+
+
+	def GetInterface(self):
+		if self.isTrue:
+			return Specification.GetInterface(self)
+		else:
+			return self.Else.GetInterface()
+
+
+	def GetVariants(self):
+		if self.isTrue:
+			return Specification.GetVariants(self)
+		else:
+			return self.Else.GetVariants()
+
+
+	def SetParentSpec(self, parent):
+		# base class method
+		Specification.SetParentSpec(self, parent)
+		# same for Else part
+		self.Else.SetParentSpec(parent)
+
+
+	def GetChildSpecs(self):
+		if self.isTrue:
+			return Specification.GetChildSpecs(self)
+		else:
+			return self.Else.GetChildSpecs()
+
+
+class Parameter(Model):
+
+	def __init__(self, name = None, default = None):
+		Model.__init__(self)	# base class constructor
+		self.name = name
+		self.default = default
+
+
+	def SetProperty(self, name, value):
+		if name == "name":
+			self.name = value
+		elif name == "default":
+			self.default = value
+		else:
+			raise InvalidPropertyError()
+
+
+	def Valid(self):
+		return (self.name != None)
+
+class ParameterGroup(Model):
+	"""A group of Parameters specified in an interface by a regexp"""
+	def __init__(self, pattern = None, default = None):
+		Model.__init__(self)	# base class constructor
+		self.pattern = pattern
+
+		self.patternre = None
+		if pattern:
+			try:
+				self.patternre = re.compile(pattern)
+			except TypeError:
+				pass
+		self.default = default
+
+
+	def SetProperty(self, pattern, value):
+		if pattern == "pattern":
+			self.pattern = value
+			self.patternre = re.compile(value)
+		elif pattern == "default":
+			self.default = value
+		else:
+			raise InvalidPropertyError()
+
+
+	def Valid(self):
+		return (self.pattern != None and self.patternre != None)
+
+
+class Operation(Model):
+	"Base class for variant operations"
+	def __init__(self):
+		Model.__init__(self)	# base class constructor
+		self.type = None
+
+
+	def Apply(self, oldValue):
+		pass
+
+
+class Append(Operation):
+	__slots__ = ('name','value','separator','owner')
+
+	def __init__(self, name = None, value = None, separator = " "):
+		Operation.__init__(self)	# base class constructor
+		self.name = name
+		self.value = value
+		self.separator = separator
+
+
+	def DebugPrint(self, prefix = ""):
+		attributes = "name='" + self.name + "' value='" + self.value + "' separator='" + self.separator + "'"
+		self.owner.Debug("%s<append %s/>", prefix, attributes)
+
+
+	def Apply(self, oldValue):
+		if len(oldValue) > 0:
+			if len(self.value) > 0:
+				return oldValue + self.separator + self.value
+			else:
+				return oldValue
+		else:
+			return self.value
+
+
+	def SetProperty(self, name, value):
+		if name == "name":
+			self.name = value
+		elif name == "value":
+			self.value = value
+		elif name == "separator":
+			self.separator = value
+		else:
+			raise InvalidPropertyError()
+
+
+	def Valid(self):
+		return (self.name != None and self.value != None)
+
+
+class Prepend(Operation):
+	def __init__(self, name = None, value = None, separator = " "):
+		Operation.__init__(self)	# base class constructor
+		self.name = name
+		self.value = value
+		self.separator = separator
+
+
+	def DebugPrint(self, prefix = ""):
+		attributes = "name='" + self.name + "' value='" + self.value + "' separator='" + self.separator + "'"
+		self.owner.Debug("%s<prepend %s/>", prefix, attributes)
+
+
+	def Apply(self, oldValue):
+		if len(oldValue) > 0:
+			if len(self.value) > 0:
+				return self.value + self.separator + oldValue
+			else:
+				return oldValue
+		else:
+			return self.value
+
+
+	def SetProperty(self, name, value):
+		if name == "name":
+			self.name = value
+		elif name == "value":
+			self.value = value
+		elif name == "separator":
+			self.separator = value
+		else:
+			raise InvalidPropertyError()
+
+
+	def Valid(self):
+		return (self.name != None and self.value != None)
+
+
+class Set(Operation):
+	"""implementation of <set> operation"""
+	__slots__ = ('name','value', 'type', 'versionCommand', 'versionResult', 'owner')
+
+	def __init__(self, name = None, value = "", type = ""):
+		Operation.__init__(self)	# base class constructor
+		self.name = name
+		self.value = value
+		self.type = type
+		self.versionCommand = ""
+		self.versionResult = ""
+
+
+	def DebugPrint(self, prefix = ""):
+		attributes = "name='" + self.name + "' value='" + self.value + "' type='" + self.type + "'"
+		if type == "tool":
+			attributes += " versionCommand='" + self.versionCommand + "' versionResult='" + self.versionResult
+
+		self.owner.Debug("%s<set %s/>", prefix, attributes)
+
+
+	def Apply(self, oldValue):
+		return self.value
+
+
+	def SetProperty(self, name, value):
+		if name == "name":
+			self.name = value
+		elif name == "value":
+			self.value = value
+		elif name == "type":
+			self.type = value
+		elif name == "versionCommand":
+			self.versionCommand = value
+		elif name == "versionResult":
+			self.versionResult = value
+		elif name == "host":
+			if HostPlatform.IsKnown(value):
+				self.host = value
+		else:
+			raise InvalidPropertyError()
+
+
+	def Valid(self):
+		return (self.name != None and self.value != None)
+
+
+class Env(Set):
+	"""implementation of <env> operator"""
+
+	def __init__(self, name = None, default = None, type = ""):
+		Set.__init__(self, name, "", type)	# base class constructor
+		self.default = default
+
+
+	def DebugPrint(self, prefix = ""):
+		attributes = "name='" + self.name + "' type='" + self.type + "'"
+		if default != None:
+			attributes += " default='" + self.default + "'"
+
+		if type == "tool":
+			attributes += " versionCommand='" + self.versionCommand + "' versionResult='" + self.versionResult + "'"
+
+		self.owner.Debug("%s<env %s/>", prefix, attributes)
+
+
+	def Apply(self, oldValue):
+		try:
+			value = os.environ[self.name]
+
+			# if this value is a "path" or a "tool" then we need to make sure
+			# it is a proper absolute path in our preferred format.
+			if value and (self.type == "path" or self.type == "tool"):
+				try:
+					path = generic_path.Path(value)
+					value = str(path.Absolute())
+				except ValueError,e:
+					self.owner.Error("the environment variable %s is incorrect: %s" % (self.name, str(e)))
+					return "NO_VALUE_FOR_" + self.name
+		except KeyError:
+			if self.default != None:
+				value = self.default
+			else:
+				self.owner.Error("%s is not set in the environment and has no default", self.name)
+				return "NO_VALUE_FOR_" + self.name
+
+		return value
+
+
+	def SetProperty(self, name, value):
+		if name == "default":
+			self.default = value
+		else:
+			Set.SetProperty(self, name, value)
+
+
+	def Valid(self):
+		return (self.name != None)
+
+
+class BuildUnit(object):
+	"Represents an individual buildable unit."
+
+	def __init__(self, name, variants):
+		self.name = name
+		
+		# A list of Variant objects.
+		self.variants = variants
+
+		# Cache for the variant operations implied by this BuildUnit.
+		self.operations = []
+		self.variantKey = ""
+
+	def GetOperations(self):
+		"""Return all operations related to this BuildUnit.
+		
+		The result is cached, and so will only be computed once per BuildUnit.
+		"""
+		key = '.'.join([x.name for x in self.variants])
+		if self.variantKey != key:
+			self.variantKey = key
+			for v in self.variants:
+				self.operations.extend( v.GetAllOperationsRecursively() )
+
+		return self.operations
+
+class Config(object):
+	"""Abstract type representing an argument to the '-c' option.
+
+	The fundamental property of a Config is that it can generate one or more
+	BuildUnits.
+	"""
+
+	def __init__(self):
+		self.modifiers = []
+
+	def AddModifier(self, variant):
+		self.modifiers.append(variant)
+
+	def ClearModifiers(self):
+		self.modifiers = []
+
+	def GenerateBuildUnits(self):
+		"""Returns a list of BuildUnits.
+
+		This function must be overridden by derived classes.
+		"""
+		raise NotImplementedError()
+
+
+class Variant(Model, Config):
+
+	def __init__(self, name = ""):
+		Model.__init__(self)
+		Config.__init__(self)
+		self.name = name
+
+		# Operations defined inside this variant.
+		self.ops = []
+
+		# The name of our parent variant, if any.
+		self.extends = ""
+
+		# Any variant references used inside this variant.
+		self.variantRefs = []
+
+		self.allOperations = []
+
+	def SetProperty(self, name, value):
+		if name == "name":
+			self.name = value
+		elif name == "host":
+			if HostPlatform.IsKnown(value):
+				self.host = value
+		elif name == "extends":
+			self.extends = value
+		else:
+			raise InvalidPropertyError()
+
+	def AddChild(self, child):
+		if isinstance(child, Operation):
+			self.ops.append(child)
+		elif isinstance(child, VariantRef):
+			self.variantRefs.append(child)
+		else:
+			raise InvalidChildError()
+
+	def Valid(self):
+		return self.name
+
+	def SetOwner(self, aRaptor):
+
+		Model.SetOwner(self, aRaptor)
+
+		for r in self.variantRefs:
+			r.SetOwner(aRaptor)
+
+		for op in self.ops:
+			op.SetOwner(aRaptor)
+
+	def AddOperation(self, op):
+		self.ops.append(op)
+
+	def GetAllOperationsRecursively(self):
+		"""Returns a list of all operations in this variant.
+
+		The list elements are themselves lists; the overall structure of the
+		returned value is:
+
+		[ [ops-from-parent],[ops-from-varRefs], [ops-in-self] ]
+		"""
+
+		if not self.allOperations:
+			if self.extends:
+				parent = self.owner.cache.FindNamedVariant(self.extends)
+				self.allOperations.extend( parent.GetAllOperationsRecursively() )
+			for r in self.variantRefs:
+				for v in [ r.Resolve() ] + r.GetModifiers():
+					self.allOperations.extend( v.GetAllOperationsRecursively() )
+			self.allOperations.append(self.ops)
+
+		return self.allOperations
+
+	def GenerateBuildUnits(self):
+
+		name = self.name
+		vars = [self]
+
+		for m in self.modifiers:
+			name = name + "." + m.name
+			vars.append(m)
+
+		return [ BuildUnit(name, vars) ]
+
+	def DebugPrint(self, prefix = ""):
+
+		self.owner.Debug("%s<var name='%s' extends='%s'>", prefix, self.name, self.extends)
+		for op in self.ops:
+			op.DebugPrint(prefix + self.indent)
+
+		self.owner.Debug("%s</var>", prefix)
+
+
+class VariantRef(Reference):
+
+	def __init__(self, ref=None):
+		Reference.__init__(self, ref)
+
+	def DebugPrint(self, prefix = ""):
+		self.owner.Debug("%s<varRef ref='%s'/>", prefix, self.ref)
+
+	def Resolve(self):
+		try:
+			return self.owner.cache.FindNamedVariant(self.ref)
+		except KeyError:
+			raise BadReferenceError(self.ref)
+
+
+class Alias(Model, Config):
+
+	def __init__(self, name=""):
+		Model.__init__(self)
+		Config.__init__(self)
+		self.name = name
+		self.meaning = ""
+		self.varRefs = []
+		self.variants = []
+
+	def DebugPrint(self, prefix = ""):
+		self.owner.Debug("%s<alias name='%s' meaning='%s'/>", prefix, self.name, self.meaning)
+
+	def SetProperty(self, key, val):
+		if key == "name":
+			self.name = val
+		elif key == "meaning":
+			self.meaning = val
+
+			for u in val.split("."):
+				self.varRefs.append( VariantRef(u) )
+		else:
+			raise InvalidPropertyError()
+
+	def SetOwner(self, raptor):
+		Model.SetOwner(self, raptor)
+		for r in self.varRefs:
+			r.SetOwner(raptor)
+
+	def Valid(self):
+		return self.name and self.meaning
+
+	def GenerateBuildUnits(self):
+		if not self.variants:
+			for r in self.varRefs:
+				try:
+					self.variants.append( r.Resolve() )
+				except BadReferenceError:
+					self.owner.Error("Missing variant '%s'", r.ref)
+
+		name = self.name
+
+		for v in self.modifiers:
+			name = name + "." + v.name
+
+		return [ BuildUnit(name, self.variants + self.modifiers) ]
+
+
+class AliasRef(Reference):
+
+	def __init__(self, ref=None):
+		Reference.__init__(self, ref)
+
+	def DebugPrint(self, prefix = ""):
+		self.owner.Debug("%s<aliasRef ref='%s'/>", prefix, self.ref)
+
+	def Resolve(self):
+		try:
+			return self.owner.cache.FindNamedAlias(self.ref)
+		except KeyError:
+			raise BadReferenceError(self.ref)
+
+
+class Group(Model, Config):
+	def __init__(self, name=""):
+		Model.__init__(self)
+		Config.__init__(self)
+		self.name = name
+		self.childRefs = []
+
+	def SetProperty(self, key, val):
+		if key == "name":
+			self.name = val
+		else:
+			raise InvalidPropertyError()
+
+	def AddChild(self, child):
+		if isinstance( child, (VariantRef,AliasRef,GroupRef) ):
+			self.childRefs.append(child)
+		else:
+			raise InvalidChildError()
+
+	def SetOwner(self, raptor):
+		Model.SetOwner(self, raptor)
+		for r in self.childRefs:
+			r.SetOwner(raptor)
+
+	def Valid(self):
+		return self.name and self.childRefs
+
+	def DebugPrint(self, prefix = ""):
+
+		self.owner.Debug("<group name='%s'>", prefix, self.name)
+
+		for r in self.childRefs:
+			r.DebugPrint(prefix + self.indent)
+
+		self.owner.Debug("%s</group>", prefix)
+
+	def GenerateBuildUnits(self):
+
+		units = []
+
+		for r in self.childRefs:
+			refMods = r.GetModifiers()
+
+			try:
+				obj = r.Resolve()
+			except BadReferenceError:
+				self.owner.Error("Missing variant '%s'", r.ref)
+			else:
+				obj.ClearModifiers()
+
+				for m in refMods + self.modifiers:
+					obj.AddModifier(m)
+
+				units.extend( obj.GenerateBuildUnits() )
+
+		return units
+
+
+class GroupRef(Reference):
+
+	def __init__(self, ref=None):
+		Reference.__init__(self, ref)
+
+	def DebugPrint(self, prefix = ""):
+		mod = ".".join(self.modifiers)
+		self.owner.Debug("%s<groupRef ref='%s' mod='%s'/>", prefix, self.ref, mod)
+
+	def Resolve(self):
+		try:
+			return self.owner.cache.FindNamedGroup(self.ref)
+		except KeyError:
+			raise BadReferenceError(self.ref)
+
+class Tool(object):
+	"""Represents a tool that might be used by raptor e.g. a compiler"""
+
+	# For use in dealing with tools that return non-ascii version strings.
+	nonascii = ""
+	identity_chartable = chr(0)
+	for c in xrange(1,128):
+		identity_chartable += chr(c)
+	for c in xrange(128,256):
+		nonascii += chr(c)
+		identity_chartable += " "
+
+	def __init__(self, name, command, versioncommand, versionresult, id="", log = raptor_utilities.nulllog):
+		self.name = name
+		self.command = command
+		self.versioncommand = versioncommand
+		self.versionresult = versionresult
+		self.id = id # what config this is from - used in debug messages
+		self.date = None
+
+
+		# Assume the tool is unavailable or the wrong
+		# version until someone proves that it's OK
+		self.valid = False
+
+		self.log=log
+
+	def expand(self, toolset):
+		self.versioncommand = toolset.ExpandAll(self.versioncommand)
+		self.versionresult  = toolset.ExpandAll(self.versionresult)
+		self.command = toolset.ExpandAll(self.command)
+		self.key = hashlib.md5(self.versioncommand + self.versionresult).hexdigest()
+		
+		# We need the tool's date to find out if we should check it.
+		try:
+			if '/' in self.command:
+				testfile = os.path.abspath(self.command.strip("\"'"))
+			else:
+				# The tool isn't a relative or absolute path so the could be relying on the 
+				# $PATH variable to make it available.  We must find the tool if it's a simple 
+				# executable file (e.g. "armcc" rather than "python myscript.py") then get it's date. 
+				# We can use the date later to see if our cache is valid. 
+				# If it really is not a simple command then we won't be able to get a date and
+				# we won't be able to tell if it is altered or updated - too bad!
+				testfile = generic_path.Where(self.command)
+				self.log.Debug("toolcheck: tool '%s' was found on the path at '%s' ", self.command, testfile)
+				if testfile is None:
+					raise Exception("Can't be found in path")
+
+			if not os.path.isfile(testfile):
+				raise Exception("tool %s appears to not be a file %s", self.command, testfile)
+				
+			testfile_stat = os.stat(testfile)
+			self.date = testfile_stat.st_mtime
+		except Exception,e:
+			self.log.Debug("toolcheck: '%s=%s' cannot be dated - this is ok, but the toolcheck won't be able to tell when a new  of the tool is installed. (%s)", self.name, self.command, str(e))
+	
+			
+	def check(self, shell, evaluator):
+
+		self.vre = re.compile(self.versionresult)
+
+		try:
+			self.log.Debug("Pre toolcheck: '%s' for version '%s'", self.name, self.versionresult)
+			p = subprocess.Popen(args=[shell, "-c", self.versioncommand], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+			versionoutput,err = p.communicate()
+			self.log.Debug("Checking tool '%s' for version '%s'", self.name, self.versionresult)
+		except Exception,e:
+			versionoutput=None
+
+		# Some tools return version strings with unicode characters! 
+		# There is no good response other than a lot of decoding and encoding.
+		# Simpler to ignore it:
+		versionoutput_a = versionoutput.translate(Tool.identity_chartable,"")
+
+		if versionoutput_a and self.vre.search(versionoutput_a) != None:
+			self.log.Debug("tool '%s' returned an acceptable version '%s' at %s", self.name, versionoutput_a, str(self.date))
+			self.valid = True
+		else:
+			self.log.Error("tool '%s' from config '%s' did not return version '%s' as required.\nCommand '%s' returned:\n%s\nCheck your environment and configuration.\n", self.name, self.id, self.versionresult, self.versioncommand, versionoutput_a)
+			self.valid = False
+		return self.valid
+
+def envhash(irrelevant_vars):
+	"""Determine something unique about this environment to identify it.
+	must ignore variables that change without mattering to the caller
+	e.g. perhaps PATH matters but PWD and PPID don't"""
+	envid = hashlib.md5()
+	for k in os.environ:
+		if k not in irrelevant_vars:
+			envid.update(os.environ[k])
+	return envid.hexdigest()[:16]
+
+
+class ToolSet(object):
+	""" 
+	This class manages a bunch of tools and keeps a cache of
+	all tools that it ever sees (across all configurations).
+	toolset.check() is called for each config but the cache is kept across calls to
+	catch the use of one tool in many configs.
+	write() is used to flush the cache to disc.
+	"""
+	# The raptor shell - this is not mutable.
+	hostbinaries = os.path.join(os.environ['SBS_HOME'], 
+	                            os.environ['HOSTPLATFORM_DIR'])
+	                            
+	if HostPlatform.IsHost('lin*'):
+		shell=os.path.join(hostbinaries, 'bin/bash')
+	else:
+		if 'SBS_CYGWIN' in os.environ:
+			shell=os.path.join(os.environ['SBS_CYGWIN'], 'bin\\bash.exe')
+		else:
+			shell=os.path.join(hostbinaries, 'cygwin\\bin\\bash.exe')
+
+
+	irrelevant_vars = ['PWD','OLDPWD','PID','PPID', 'SHLVL' ]
+
+
+	shell_version=".*GNU bash, version [34].*"
+	shell_re = re.compile(shell_version)
+	if 'SBS_BUILD_DIR' in os.environ:
+		cachefile_basename = str(generic_path.Join(os.environ['SBS_BUILD_DIR'],"toolcheck_cache_"))
+	elif 'EPOCROOT' in os.environ:
+		cachefile_basename = str(generic_path.Join(os.environ['EPOCROOT'],"epoc32/build/toolcheck_cache_"))
+	else:
+		cachefile_basename = None
+
+	tool_env_id = envhash(irrelevant_vars)
+	filemarker = "sbs_toolcache_2.8.2"
+
+	def __init__(self, log = raptor_utilities.nulllog, forced=False):
+		self.__toolcheckcache = {}
+
+		self.valid = True
+		self.checked = False
+		self.shellok = False
+		self.configname=""
+		self.cache_loaded = False
+		self.forced = forced
+
+		self.log=log
+
+		# Read in the tool cache
+		#
+		# The cache format is a hash key which identifies the
+		# command and the version that we're checking for. Then
+		# there are name,value pairs that record, e.g. the date
+		# of the command file or the name of the variable that
+		# the config uses for the tool (GNUCP or MWCC or whatever)
+
+		if ToolSet.cachefile_basename:
+			self.cachefilename = ToolSet.cachefile_basename+".tmp"
+			if not self.forced:
+				try:
+					f = open(self.cachefilename, "r+")
+					# if this tool cache was recorded in
+					# a different environment then ignore it.
+					marker = f.readline().rstrip("\r\n")
+					if marker == ToolSet.filemarker:
+						env_id_tmp = f.readline().rstrip("\r\n")
+						if env_id_tmp == ToolSet.tool_env_id:
+							try:
+								for l in f.readlines():
+									toolhistory  = l.rstrip(",\n\r").split(",")
+									ce = {}
+									for i in toolhistory[1:]:
+										(name,val) = i.split("=")
+										if name == "valid":
+											val = bool(val)
+										elif name == "age":
+											val = int(val)
+										elif name == "date":
+											if val != "None":
+												val = float(val)
+											else:
+												val= None
+
+										ce[name] = val
+									self.__toolcheckcache[toolhistory[0]] = ce
+								log.Info("Loaded toolcheck cache: %s\n", self.cachefilename)
+							except Exception, e:
+								log.Info("Ignoring garbled toolcheck cache: %s (%s)\n", self.cachefilename, str(e))
+								self.__toolcheckcache = {}
+								
+									
+						else:
+							log.Info("Toolcheck cache %s ignored - environment changed\n", self.cachefilename)
+					else:
+						log.Info("Toolcheck cache not loaded = marker missing: %s %s\n", self.cachefilename, ToolSet.filemarker)
+					f.close()
+				except IOError, e:
+					log.Info("Failed to load toolcheck cache: %s\n", self.cachefilename)
+		else:
+			log.Debug("Toolcheck cachefile not created because EPOCROOT not set in environment.\n")
+
+	def check_shell(self):
+		# The command shell is a critical tool because all the other tools run
+		# within it so we must check for it first. It has to be in the path.
+		# bash 4 is preferred, 3 is accepted
+		try:
+			p = subprocess.Popen(args=[ToolSet.shell, '--version'], bufsize=1024, shell = False, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True)
+			shellversion_out, errtxt = p.communicate()
+			if ToolSet.shell_re.search(shellversion_out) == None:
+				self.log.Error("A critical tool, '%s', did not return the required version '%s':\n%s\nPlease check that '%s' is in the path.", ToolSet.shell, ToolSet.shell_version, shellversion_out, ToolSet.shell)
+				self.valid = False
+		except Exception,e:
+			self.log.Error("A critical tool could not be found.\nPlease check that '%s' is in the path. (%s)", ToolSet.shell,  str(e))
+			self.valid = False
+
+		return self.valid
+
+	def check(self, evaluator, configname):
+		"""Check the toolset for a particular config"""
+
+		self.checked = True # remember that we did check something
+
+		if not self.shellok:
+			self.shellok = self.check_shell()
+		self.shellok = True
+
+		self.valid = self.valid and self.shellok
+
+		cache = self.__toolcheckcache 
+		for tool in evaluator.tools:
+			if not self.forced:
+				try:
+					t = cache[tool.key]
+						
+				except KeyError,e:
+					pass
+				else:
+					# if the cache has an entry for the tool then see if the date on
+					# the tool has changed (assuming the tool is a simple executable file)
+					if t.has_key('date') and (tool.date is None or (tool.date - t['date'] > 0.1))  :
+						self.log.Debug("toolcheck forced: '%s'  changed since the last check: %s < %s", tool.command, str(t['date']), str(tool.date))
+					else:
+						t['age'] = 0 # we used it so it's obviously needed
+						self.valid = self.valid and t['valid']
+						self.log.Debug("toolcheck saved on: '%s'", tool.name)
+						continue
+
+
+			self.log.Debug("toolcheck done: %s -key: %s" % (tool.name, tool.key))
+
+			if not tool.check(ToolSet.shell, evaluator):
+				self.valid = False
+
+			# Tool failures are cached just like successes - don't want to repeat them
+			cache[tool.key] =  { "name" : tool.name, "valid" : tool.valid, "age" : 0 , "date" : tool.date }
+
+
+	def write(self):
+		"""Writes the tool check cache to disc.
+
+		   toolset.write()
+		"""
+		cache = self.__toolcheckcache 
+
+		# Write out the cache.
+		if self.checked and ToolSet.cachefile_basename:
+			self.log.Debug("Saving toolcache: %s", self.cachefilename)
+			try:
+				f = open(self.cachefilename, "w+")
+				f.write(ToolSet.filemarker+"\n")
+				f.write(ToolSet.tool_env_id+"\n")
+				for k,ce in cache.iteritems():
+
+					# If a tool has not been used for an extraordinarily long time
+					# then forget it - to prevent the cache from clogging up with old tools.
+					# Only write entries for tools that were found to be ok - so that the 
+					# next time the ones that weren't will be re-tested
+
+					if ce['valid'] and ce['age'] < 100:
+						ce['age'] += 1
+						f.write("%s," % k)
+						for n,v in ce.iteritems():
+							f.write("%s=%s," % (n,str(v)))
+					f.write("\n")
+				f.close()
+				self.log.Info("Created/Updated toolcheck cache: %s\n", self.cachefilename)
+			except Exception, e:
+				self.log.Info("Could not write toolcheck cache: %s", str(e))
+		return self.valid
+
+
+class Evaluator(object):
+	"""Determine the values of variables under different Configurations.
+	Either of specification and buildUnit may be None."""
+
+
+	refRegex = re.compile("\$\((.+?)\)")
+
+	def __init__(self, Raptor, specification, buildUnit, gathertools = False):
+		self.raptor = Raptor
+		self.dict = {}
+		self.tools = []
+		self.gathertools = gathertools
+
+		specName = "none"
+		configName = "none"
+
+		# A list of lists of operations.
+		opsLists = []
+
+		if buildUnit:
+			opsLists.extend( buildUnit.GetOperations() )
+
+		if specification:
+			for v in specification.GetAllVariantsRecursively():
+				opsLists.extend( v.GetAllOperationsRecursively() )
+
+		tools = {}
+
+		for opsList in opsLists:
+			for op in opsList:
+				# applying an Operation to a non-existent variable
+				# is OK. We assume that it is just an empty string.
+				try:
+					oldValue = self.dict[op.name]
+				except KeyError:
+					oldValue = ""
+
+				newValue = op.Apply(oldValue)
+				self.dict[op.name] = newValue
+			
+				if self.gathertools:
+					if op.type == "tool" and op.versionCommand and op.versionResult:
+						tools[op.name] = Tool(op.name, newValue, op.versionCommand, op.versionResult, configName, log = self.raptor)
+
+
+		if self.gathertools:
+			self.tools = tools.values()
+		else:
+			self.tools=[]
+
+		# resolve inter-variable references in the dictionary
+		unresolved = True
+
+		for k, v in self.dict.items():
+			self.dict[k] = v.replace("$$","__RAPTOR_ESCAPED_DOLLAR__")
+
+		while unresolved:
+			unresolved = False
+			for k, v in self.dict.items():
+				if v.find('$(' + k + ')') != -1:
+					self.raptor.Error("Recursion Detected in variable '%s' in configuration '%s' ",k,configName)
+					expanded = "RECURSIVE_INVALID_STRING"
+				else:
+					expanded = self.ExpandAll(v, specName, configName)
+
+				if expanded != v:				# something changed?
+					self.dict[k] = expanded
+					unresolved = True			# maybe more to do
+
+		# unquote double-dollar references
+		for k, v in self.dict.items():
+			self.dict[k] = v.replace("__RAPTOR_ESCAPED_DOLLAR__","$")
+
+		for t in self.tools:
+			t.expand(self)
+
+
+
+	def Get(self, name):
+		"""return the value of variable 'name' or None if not found."""
+
+		if name in self.dict:
+			return self.dict[name]
+		else:
+			return None
+
+
+	def Resolve(self, name):
+		"""same as Get except that env variables are expanded.
+
+		raises BadReferenceError if the variable 'name' exists but a
+		contained environment variable does not exist."""
+		return self.Get(name) # all variables are now expanded anyway
+
+
+	def ResolveMatching(self, pattern):
+		""" Return a dictionary of all variables that match the pattern """
+		for k,v in self.dict.iteritems():
+			if pattern.match(k):
+				yield (k,v)
+
+
+	def ExpandAll(self, value, spec = "none", config = "none"):
+		"""replace all $(SOMETHING) in the string value.
+
+		returns the newly expanded string."""
+
+		refs = Evaluator.refRegex.findall(value)
+
+		for r in set(refs):
+			expansion = None
+
+			if r in self.raptor.override:
+				expansion = self.raptor.override[r]
+			elif r in self.dict:
+				expansion = self.dict[r]
+			else:
+				# no expansion for $(r)
+				self.raptor.Error("Unset variable '%s' used in spec '%s' with config '%s'",
+							  	  r, spec, config)
+			if expansion != None:
+				value = value.replace("$(" + r + ")", expansion)
+
+		return value
+
+
+# raptor_data module functions
+
+
+# end of the raptor_data module