sbsv2/raptor/python/raptor_make.py
author raptorbot <raptorbot@systemstesthead.symbian.intra>
Fri, 18 Dec 2009 19:57:42 +0000
branchwip
changeset 117 ecf683438dc6
parent 29 ee00c00df073
child 118 375b7128e900
permissions -rw-r--r--
Don't mess around with EPOCROOT until actually entering raptor so we know what the original was Put the original epocroot back on the front of the whatcomp output. This allows what output to be either relative or absolute depending on what your epocroot is.

#
# 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_make module
# This module contains the classes that write and call Makefile wrappers.
#

import hashlib
import os
import random
import raptor
import raptor_timing
import raptor_utilities
import raptor_version
import raptor_data
import re
import subprocess
import time
from raptor_makefile import *
import traceback
import sys

# raptor_make module classes

class MakeEngine(object):

	def __init__(self, Raptor):
		self.raptor = Raptor
		self.valid = True
		self.descrambler = None
		self.descrambler_started = False

		engine = Raptor.makeEngine
		
		# look for an alias first as this gives end-users a chance to modify
		# the shipped variant rather than completely replacing it.
		if engine in Raptor.cache.aliases:
			avar = Raptor.cache.FindNamedAlias(engine)
		elif engine in Raptor.cache.variants:
			avar = Raptor.cache.FindNamedVariant(engine)
		else:
			Raptor.Error("No settings found for build engine '%s'", engine)
			return
					
		# find the variant and extract the values
		try:
			units = avar.GenerateBuildUnits(Raptor.cache)
			evaluator = Raptor.GetEvaluator( None, units[0] , gathertools=True)

			# shell
			self.shellpath = evaluator.Get("DEFAULT_SHELL")
			usetalon_s = evaluator.Get("USE_TALON") 
			self.usetalon = usetalon_s is not None and usetalon_s != ""
			self.talonshell = str(evaluator.Get("TALON_SHELL"))
			self.talontimeout = str(evaluator.Get("TALON_TIMEOUT"))
			self.talonretries = str(evaluator.Get("TALON_RETRIES"))

			# commands
			self.initCommand = evaluator.Get("initialise")
			self.buildCommand = evaluator.Get("build")
			self.shutdownCommand = evaluator.Get("shutdown")

			# options
			self.makefileOption = evaluator.Get("makefile")
			self.keepGoingOption = evaluator.Get("keep_going")
			self.jobsOption = evaluator.Get("jobs")
			self.defaultMakeOptions = evaluator.Get("defaultoptions")

			# buffering
			self.scrambled = (evaluator.Get("scrambled") == "true")

			# check tool versions
			Raptor.CheckToolset(evaluator, avar.name)
			
			# default targets (can vary per-invocation)
			self.defaultTargets = Raptor.defaultTargets

			# work out how to split up makefiles
			try:
				selectorNames = [ x.strip() for x in evaluator.Get("selectors").split(',') if x.strip() != "" ]
				self.selectors = []


				if len(selectorNames) > 0:
					for name in selectorNames:
						pattern = evaluator.Get(name.strip() + ".selector.iface")
						target = evaluator.Get(name.strip() + ".selector.target")
						ignoretargets = evaluator.Get(name.strip() + ".selector.ignoretargets")
						self.selectors.append(MakefileSelector(name,pattern,target,ignoretargets))
			except KeyError:
				Raptor.Error("%s.selector.iface, %s.selector.target not found in make engine configuration", name, name)
				self.selectors = []

		except KeyError:
			Raptor.Error("Bad '%s' configuration found.", engine)
			self.valid = False
			return

		# there must at least be a build command...
		if not self.buildCommand:
				Raptor.Error("No build command for '%s'", engine)
				self.valid = False


		if self.usetalon:
			talon_settings="""
TALON_SHELL:=%s
TALON_TIMEOUT:=%s
TALON_RECIPEATTRIBUTES:=\
 name='$$RECIPE'\
 target='$$TARGET'\
 host='$$HOSTNAME'\
 layer='$$COMPONENT_LAYER'\
 component='$$COMPONENT_NAME'\
 bldinf='$$COMPONENT_META' mmp='$$PROJECT_META'\
 config='$$SBS_CONFIGURATION' platform='$$PLATFORM'\
 phase='$$MAKEFILE_GROUP' source='$$SOURCE'
export TALON_RECIPEATTRIBUTES TALON_SHELL TALON_TIMEOUT
USE_TALON:=%s

""" % (self.talonshell, self.talontimeout, "1")
		else:
			talon_settings="""
USE_TALON:=

"""


		timing_start = "$(info " + \
				raptor_timing.Timing.custom_string(tag = "start",
				object_type = "makefile", task = "parse",
				key = "$(THIS_FILENAME)",
				time="$(shell date +%s.%N)").rstrip("\n") + ")"
				
		timing_end = "$(info " + \
				raptor_timing.Timing.custom_string(tag = "end",
				object_type = "makefile", task = "parse",
				key = "$(THIS_FILENAME)",
				time="$(shell date +%s.%N)").rstrip("\n") + ")"


		self.makefile_prologue = """

# generated by %s %s

HOSTPLATFORM:=%s
HOSTPLATFORM_DIR:=%s
OSTYPE:=%s
FLMHOME:=%s
SHELL:=%s
THIS_FILENAME:=$(firstword $(MAKEFILE_LIST))

%s

include %s

""" 		% (  raptor.name, raptor_version.fullversion(),
			 " ".join(raptor.hostplatform),
			 raptor.hostplatform_dir,
			 self.raptor.filesystem,
			 str(self.raptor.systemFLM),
			 self.shellpath,
			 talon_settings,
			 self.raptor.systemFLM.Append('globals.mk') )

		# Only output timings if requested on CLI
		if self.raptor.timing:
			self.makefile_prologue += "\n# Print Start-time of Makefile parsing\n" \
					+ timing_start + "\n\n"
	
	
			self.makefile_epilogue = "\n\n# Print End-time of Makefile parsing\n" \
				+ timing_end + "\n"
		else:
			self.makefile_epilogue = ""

		self.makefile_epilogue += """

include %s

""" 			% (self.raptor.systemFLM.Append('final.mk') )

	def Write(self, toplevel, specs, configs):
		"""Generate a set of makefiles, or one big Makefile."""

		if not self.valid:
			return None

		self.raptor.Debug("Writing Makefile '%s'" % (str(toplevel)))

		self.toplevel = toplevel

		# create the top-level makefiles
		makefileset = None

		try:
			makefileset = MakefileSet(directory = str(toplevel.Dir()),
										   selectors = self.selectors,
										   filenamebase = str(toplevel.File()),
										   prologue = self.makefile_prologue,
										   epilogue = self.makefile_epilogue,
										   defaulttargets = self.defaultTargets)

			# are we pruning duplicates?
			self.prune = self.raptor.pruneDuplicateMakefiles
			self.hashes = set()

			# are we writing one Makefile or lots?
			self.many = not self.raptor.writeSingleMakefile

			# add a makefile for each spec under each config
			config_makefileset = makefileset
			for c in configs:
				if self.many:
					config_makefileset = makefileset.createChild(c.name)

				# make sure the config_wide spec item is put out first so that it
				# can affect everything.
				ordered_specs=[]
				config_wide_spec = None
				for s in specs:
					if s.name == "config_wide":
						config_wide_spec = s
					else:
						ordered_specs.append(s)

				if config_wide_spec is not None:
					config_wide_spec.Configure(c, cache = self.raptor.cache)
					self.WriteConfiguredSpec(config_makefileset, config_wide_spec, c, True)

				for s in ordered_specs:
					s.Configure(c, cache = self.raptor.cache)
					self.WriteConfiguredSpec(config_makefileset, s, c, False)

			makefileset.close()
		except Exception,e:
			tb = traceback.format_exc()
			if not self.raptor.debugOutput:
				tb=""
			self.raptor.Error("Failed to write makefile '%s': %s : %s" % (str(toplevel),str(e),tb))
			makefileset = None

		return makefileset


	def WriteConfiguredSpec(self, parentMakefileSet, spec, config, useAllInterfaces):
		# ignore this spec if it is empty
		hasInterface = spec.HasInterface()
		childSpecs = spec.GetChildSpecs()

		if not hasInterface and not childSpecs:
			return

		parameters = []
		dupe = True
		iface = None
		guard = None
		if hasInterface:
			# find the Interface (it may be a ref)
			try:
				iface = spec.GetInterface(self.raptor.cache)

			except raptor_data.MissingInterfaceError, e:	
				self.raptor.Error("No interface for '%s'", spec.name)
				return

			if iface.abstract:
				self.raptor.Error("Abstract interface '%s' for '%s'",
								  iface.name, spec.name)
				return

			# we need to guard the FLM call with a hash based on all the
			# parameter values so that duplicate calls cannot be made.
			# So we need to find all the values before we can write
			# anything out.
			md5hash = hashlib.md5()
			md5hash.update(iface.name)

			# we need an Evaluator to get parameter values for this
			# Specification in the context of this Configuration
			evaluator = self.raptor.GetEvaluator(spec, config)

			def addparam(k, value, default):
				if value == None:
					if p.default != None:
						value = p.default
					else:
						self.raptor.Error("%s undefined for '%s'",
										  k, spec.name)
						value = ""

				parameters.append((k, value))
				md5hash.update(value)

			# parameters required by the interface
			for p in iface.GetParams(self.raptor.cache):
				val = evaluator.Resolve(p.name)
				addparam(p.name,val,p.default)

			# Use Patterns to fetch a group of parameters
			for g in iface.GetParamGroups(self.raptor.cache):
				for k,v in evaluator.ResolveMatching(g.patternre):
					addparam(k,v,g.default)

			hash = md5hash.hexdigest()
			dupe = hash in self.hashes

			self.hashes.add(hash)

		# we only create a Makefile if we have a new FLM call to contribute,
		# OR we are not pruning duplicates (guarding instead)
		# OR we have some child specs that need something to include them.
		if dupe and self.prune and not childSpecs:
			return

		makefileset = parentMakefileSet
		# Create a new layer of makefiles?
		if self.many:
			makefileset = makefileset.createChild(spec.name)

		if not (self.prune and dupe):
			if self.prune:
				guard = ""
			else:
				guard = "guard_" + hash

		# generate the call to the FLM
		if iface is not None:
			makefileset.addCall(spec.name, config.name, iface.name, useAllInterfaces, iface.GetFLMIncludePath(self.raptor.cache), parameters, guard)

		# recursive includes

		for child in childSpecs:
			self.WriteConfiguredSpec(makefileset, child, config, useAllInterfaces)

		if self.many:
			makefileset.close() # close child set of makefiles as we'll never see them again.

	def Make(self, makefileset):
		"run the make command"

		if not self.valid:
			return False
	
		if self.usetalon:
			# Always use Talon since it does the XML not
			# just descrambling
			if not self.StartTalon() and not self.raptor.keepGoing:
				self.Tidy()
				return False
		else:
			# use the descrambler if we are doing a parallel build on
			# a make engine which does not buffer each agent's output
			if self.raptor.jobs > 1 and self.scrambled:
				self.StartDescrambler()
				if  not self.descrambler_started and not self.raptor.keepGoing:
					self.Tidy()
					return False
			
		# run any initialisation script
		if self.initCommand:
			self.raptor.Info("Running %s", self.initCommand)
			if os.system(self.initCommand) != 0:
				self.raptor.Error("Failed in %s", self.initCommand)
				self.Tidy()
				return False

		# Save file names to a list, to allow the order to be reversed
		fileName_list = list(makefileset.makefileNames())

		# Iterate through args passed to raptor, searching for CLEAN or REALLYCLEAN
		clean_flag = False
		for arg in self.raptor.args:
			clean_flag = ("CLEAN" in self.raptor.args) or \
			            ("REALLYCLEAN" in self.raptor.args)

		# Files should be deleted in the opposite order to the order
		# they were built. So reverse file order if cleaning
		if clean_flag:
			fileName_list.reverse()

		# Report number of makefiles to be built
		self.raptor.InfoDiscovery(object_type = "makefile", count = len(fileName_list))

		# Process each file in turn
		for makefile in fileName_list:
			if not os.path.exists(makefile):
				self.raptor.Info("Skipping makefile %s", makefile)
				continue
			self.raptor.Info("Making %s", makefile)
			# assemble the build command line
			command = self.buildCommand

			if self.makefileOption:
				command += " " + self.makefileOption + " " + '"' + str(makefile) + '"'

			if self.raptor.keepGoing and self.keepGoingOption:
				command += " " + self.keepGoingOption

			if self.raptor.jobs > 1 and self.jobsOption:
				command += " " + self.jobsOption +" "+ str(self.raptor.jobs)

			# Set default options first so that they can be overridden by
			# ones set by the --mo option on the raptor commandline:
			command += " " + self.defaultMakeOptions
			# Can supply options on the commandline to override default settings.
			if len(self.raptor.makeOptions) > 0:
				command += " " + " ".join(self.raptor.makeOptions)

			# Switch off dependency file including?
			if self.raptor.noDependInclude:
				command += " NO_DEPEND_INCLUDE=1"
			
			if self.usetalon:
				# use the descrambler if we set it up
				command += ' TALON_DESCRAMBLE=' 
				if self.scrambled:
					command += '1 '
				else:
					command += '0 '
			else:
				if self.descrambler_started:
					command += ' DESCRAMBLE="' + self.descrambler + '"'
			
			# use the retry mechanism if requested
			if self.raptor.tries > 1:
				command += ' RECIPETRIES=' + str(self.raptor.tries)
				command += ' TALON_RETRIES=' + str(self.raptor.tries - 1)

			# targets go at the end, if the makefile supports them
			addTargets = self.raptor.targets[:]
			ignoreTargets = makefileset.ignoreTargets(makefile)
			if addTargets and ignoreTargets:
				for target in self.raptor.targets:
					if re.match(ignoreTargets, target):
						addTargets.remove(target)

			if addTargets:
				command += " " + " ".join(addTargets)

			# Substitute the makefile name for any occurrence of #MAKEFILE#
			command = command.replace("#MAKEFILE#", str(makefile))

			self.raptor.Info("Executing '%s'", command)

			# execute the build.
			# the actual call differs between Windows and Unix.
			# bufsize=1 means "line buffered"
			#
			try:
				# Time the build
				self.raptor.InfoStartTime(object_type = "makefile",
						task = "build", key = str(makefile))
				
				makeenv=os.environ.copy()
				if self.usetalon:
					makeenv['TALON_RECIPEATTRIBUTES']="none"
					makeenv['TALON_SHELL']=self.talonshell
					makeenv['TALON_BUILDID']=str(self.buildID)
					makeenv['TALON_TIMEOUT']=str(self.talontimeout)
				if self.raptor.filesystem == "unix":
					p = subprocess.Popen(command, bufsize=65535,
									     stdout=subprocess.PIPE,
									     stderr=subprocess.STDOUT,
									     close_fds=True, env=makeenv, shell=True)
				else:
					p = subprocess.Popen(command, bufsize=65535,
									     stdout=subprocess.PIPE,
									     stderr=subprocess.STDOUT,
									     universal_newlines=True, env=makeenv)
				stream = p.stdout


				line = " "
				while line:
					line = stream.readline()
					self.raptor.out.write(line)

				# should be done now
				returncode = p.wait()

				# Report end-time of the build
				self.raptor.InfoEndTime(object_type = "makefile",
						task = "build", key = str(makefile))

				if returncode != 0  and not self.raptor.keepGoing:
					self.Tidy()
					return False

			except Exception,e:
				self.raptor.Error("Exception '%s' during '%s'", str(e), command)
				self.Tidy()
				# Still report end-time of the build
				self.raptor.InfoEnd(object_type = "Building", task = "Makefile",
						key = str(makefile))
				return False

		# run any shutdown script
		if self.shutdownCommand != None and self.shutdownCommand != "":
			self.raptor.Info("Running %s", self.shutdownCommand)
			if os.system(self.shutdownCommand) != 0:
				self.raptor.Error("Failed in %s", self.shutdownCommand)
				self.Tidy()
				return False

		self.Tidy()
		return True

	def Tidy(self):
		if self.usetalon:
			self.StopTalon() 
		else:
			"clean up after the make command"
			self.StopDescrambler()

	def StartTalon(self):
		# the talon command
		beginning = raptor.hostplatform_dir + "/bin"
		if "win" in raptor.hostplatform:
			end = ".exe"
		else:
			end = ""
			
		self.talonctl = str(self.raptor.home.Append(beginning, "talonctl"+end))
			
		# generate a unique build number
		random.seed()
		looking = True
		tries = 0
		while looking and tries < 100:
			self.buildID = raptor.name + str(random.getrandbits(32))
			
			command = self.talonctl + " start"

			os.environ["TALON_BUILDID"] = self.buildID
			self.raptor.Info("Running %s", command)
			looking = (os.system(command) != 0)
			tries += 1
		if looking:
			self.raptor.Error("Failed to initialise the talon shell for this build")
			self.talonctl = ""
			return False
		
		return True
	
	def StopTalon(self):
		if self.talonctl:
			command = self.talonctl + " stop"
			self.talonctl = ""
			
			self.raptor.Info("Running %s", command)
			if os.system(command) != 0:
				self.raptor.Error("Failed in %s", command)
				return False
			
		return True
	
	def StartDescrambler(self):
		# the descrambler command
		beginning = raptor.hostplatform_dir + "/bin"
		if "win" in raptor.hostplatform:
			end = ".exe"
		else:
			end = ""

		self.descrambler = str(self.raptor.home.Append(beginning, "sbs_descramble"+end))
			
		# generate a unique build number
		random.seed()
		looking = True
		tries = 0
		while looking and tries < 100:
			buildID = raptor.name + str(random.getrandbits(32))

			command = self.descrambler + " " + buildID + " start"
			self.raptor.Info("Running %s", command)
			looking = (os.system(command) != 0)
			tries += 1

		if looking:
			self.raptor.Error("Failed to start the log descrambler")
			self.descrambler_started = True
			return False

		self.descrambler_started = True
		self.descrambler +=	" " + buildID

		return  True

	def StopDescrambler(self):
		if self.descrambler_started:
			command = self.descrambler + " stop"
			self.descrambler = ""

			self.raptor.Info("Running %s", command)
			if os.system(command) != 0:
				self.raptor.Error("Failed in %s", command)
				return False
		return True

# raptor_make module functions


# end of the raptor_make module