2010-06-07 14:53:21 +02:00
/* sbt -- Simple Build Tool
* Copyright 2010 Mark Harrah
*/
package sbt
2010-06-11 03:25:37 +02:00
import ErrorHandling.wideConvert
2010-06-07 14:53:21 +02:00
import Types._
import Execute._
import scala.annotation.tailrec
import scala.collection. { mutable , JavaConversions }
import mutable.Map
object Execute
{
trait Part1of2K [ M [ _ [ _ ] , _ ] , A [ _ ] ] { type Apply [ T ] = M [ A , T ] }
type NodeT [ A [ _ ] ] = Part1of2K [ Node , A ]
2010-07-19 18:38:42 +02:00
type NodeView [ A [ _ ] ] = A ~> NodeT [ A ] # Apply
2010-06-07 14:53:21 +02:00
2011-01-21 23:22:18 +01:00
def idMap [ A ,B ] : Map [ A , B ] = JavaConversions . asScalaMap ( new java . util . IdentityHashMap [ A ,B ] )
2010-06-07 14:53:21 +02:00
def pMap [ A [ _ ] , B [ _ ] ] : PMap [ A ,B ] = new DelegatingPMap [ A , B ] ( idMap )
private [ sbt ] def completed ( p : => Unit ) : Completed = new Completed {
def process ( ) { p }
}
2011-08-06 03:56:32 +02:00
def noTriggers [ A [ _ ] ] = new Triggers [ A ] ( Map . empty , Map . empty , idFun )
2010-06-07 14:53:21 +02:00
}
sealed trait Completed {
def process ( ) : Unit
}
2011-08-06 03:56:32 +02:00
final class Triggers [ A [ _ ] ] ( val runBefore : collection.Map [ A [ _ ] , Seq [ A [ _ ] ] ] , val injectFor : collection.Map [ A [ _ ] , Seq [ A [ _ ] ] ] , val onComplete : RMap [ A ,Result ] => RMap [ A ,Result ] )
2010-06-07 14:53:21 +02:00
2011-04-23 17:49:58 +02:00
final class Execute [ A [ _ ] <: AnyRef ] ( checkCycles : Boolean , triggers : Triggers [ A ] ) ( implicit view : NodeView [ A ] )
2010-06-07 14:53:21 +02:00
{
type Strategy = CompletionService [ A [ _ ] , Completed ]
private [ this ] val forward = idMap [ A [ _ ] , IDSet [ A [ _ ] ] ]
private [ this ] val reverse = idMap [ A [ _ ] , Iterable [ A [ _ ] ] ]
private [ this ] val callers = pMap [ A , Compose [ IDSet ,A ] # Apply ]
private [ this ] val state = idMap [ A [ _ ] , State ]
private [ this ] val viewCache = pMap [ A , NodeT [ A ] # Apply ]
private [ this ] val results = pMap [ A , Result ]
private [ this ] type State = State . Value
private [ this ] object State extends Enumeration {
val Pending , Running , Calling , Done = Value
}
import State. { Pending , Running , Calling , Done }
def dump : String = "State: " + state . toString + "\n\nResults: " + results + "\n\nCalls: " + callers + "\n\n"
2011-04-08 04:51:25 +02:00
def run [ T ] ( root : A [ T ] ) ( implicit strategy : Strategy ) : Result [ T ] = try { runKeep ( root ) ( strategy ) ( root ) } catch { case i : Incomplete => Inc ( i ) }
2011-01-21 23:22:18 +01:00
def runKeep [ T ] ( root : A [ T ] ) ( implicit strategy : Strategy ) : RMap [ A ,Result ] =
2010-06-07 14:53:21 +02:00
{
assert ( state . isEmpty , "Execute already running/ran." )
addNew ( root )
processAll ( )
assert ( results contains root , "No result for root node." )
2011-08-06 03:56:32 +02:00
triggers . onComplete ( results )
2010-06-07 14:53:21 +02:00
}
def processAll ( ) ( implicit strategy : Strategy )
{
@tailrec def next ( )
{
pre {
assert ( ! reverse . isEmpty , "Nothing to process." )
2011-04-08 04:51:25 +02:00
if ( ! state . values . exists ( _ == Running ) ) {
snapshotCycleCheck ( )
2011-06-13 03:32:51 +02:00
assert ( false , "Internal task engine error: nothing running. This usually indicates a cycle in tasks.\n Calling tasks (internal task engine state):\n" + dumpCalling )
2011-04-08 04:51:25 +02:00
}
2010-06-07 14:53:21 +02:00
}
( strategy . take ( ) ) . process ( )
if ( ! reverse . isEmpty ) next ( )
}
next ( )
post {
assert ( reverse . isEmpty , "Did not process everything." )
assert ( complete , "Not all state was Done." )
}
}
2011-06-13 03:32:51 +02:00
def dumpCalling : String = state . filter ( _ . _2 == Calling ) . mkString ( "\n\t" )
2010-06-07 14:53:21 +02:00
def call [ T ] ( node : A [ T ] , target : A [ T ] ) ( implicit strategy : Strategy )
{
if ( checkCycles ) cycleCheck ( node , target )
pre {
assert ( running ( node ) )
readyInv ( node )
}
2010-06-10 14:17:51 +02:00
results . get ( target ) match {
case Some ( result ) => retire ( node , result )
case None =>
state ( node ) = Calling
addChecked ( target )
addCaller ( node , target )
}
2010-06-07 14:53:21 +02:00
post {
2010-06-10 14:17:51 +02:00
if ( done ( target ) )
assert ( done ( node ) )
else {
assert ( calling ( node ) )
assert ( callers ( target ) contains node )
}
2010-06-07 14:53:21 +02:00
readyInv ( node )
}
}
def retire [ T ] ( node : A [ T ] , result : Result [ T ] ) ( implicit strategy : Strategy )
{
pre {
assert ( running ( node ) | calling ( node ) )
readyInv ( node )
}
results ( node ) = result
state ( node ) = Done
remove ( reverse , node ) foreach { dep => notifyDone ( node , dep ) }
2011-04-07 02:44:54 +02:00
callers . remove ( node ) . flatten . foreach { c => retire ( c , callerResult ( c , result ) ) }
2011-04-23 17:49:58 +02:00
triggeredBy ( node ) foreach { t => addChecked ( t ) }
2010-06-07 14:53:21 +02:00
post {
assert ( done ( node ) )
assert ( results ( node ) == result )
readyInv ( node )
assert ( ! ( reverse contains node ) )
assert ( ! ( callers contains node ) )
2011-04-23 17:49:58 +02:00
assert ( triggeredBy ( node ) forall added )
2010-06-07 14:53:21 +02:00
}
}
2011-04-07 02:44:54 +02:00
def callerResult [ T ] ( node : A [ T ] , result : Result [ T ] ) : Result [ T ] =
result match {
case _ : Value [ T ] => result
case Inc ( i ) => Inc ( Incomplete ( Some ( node ) , tpe = i . tpe , causes = i : : Nil ) )
}
2010-06-07 14:53:21 +02:00
def notifyDone ( node : A [ _ ] , dependent : A [ _ ] ) ( implicit strategy : Strategy )
{
val f = forward ( dependent )
f -= node
if ( f . isEmpty ) {
remove ( forward , dependent )
ready ( dependent )
}
}
/* * Ensures the given node has been added to the system.
* Once added , a node is pending until its inputs and dependencies have completed .
* Its computation is then evaluated and made available for nodes that have it as an input . */
def addChecked [ T ] ( node : A [ T ] ) ( implicit strategy : Strategy )
{
if ( ! added ( node ) ) addNew ( node )
post { addedInv ( node ) }
}
/* * Adds a node that has not yet been registered with the system.
* If all of the node 's dependencies have finished , the node 's computation scheduled to run .
* The node 's dependencies will be added ( transitively ) if they are not already registered .
* */
def addNew [ T ] ( node : A [ T ] ) ( implicit strategy : Strategy )
{
pre { newPre ( node ) }
val v = register ( node )
2011-04-23 17:49:58 +02:00
val deps = dependencies ( v ) ++ runBefore ( node )
2011-03-01 14:52:17 +01:00
val active = IDSet [ A [ _ ] ] ( deps filter notDone )
2010-06-07 14:53:21 +02:00
if ( active . isEmpty )
ready ( node )
else
{
2011-03-01 14:52:17 +01:00
forward ( node ) = active
2010-09-18 02:16:21 +02:00
for ( a <- active )
{
addChecked ( a )
addReverse ( a , node )
}
2010-06-07 14:53:21 +02:00
}
post {
addedInv ( node )
assert ( running ( node ) ^ pending ( node ) )
if ( running ( node ) ) runningInv ( node )
if ( pending ( node ) ) pendingInv ( node )
}
}
/* * Called when a pending 'node' becomes runnable. All of its dependencies must be done. This schedules the node's computation with 'strategy'. */
def ready [ T ] ( node : A [ T ] ) ( implicit strategy : Strategy )
{
pre {
assert ( pending ( node ) )
readyInv ( node )
assert ( reverse contains node )
}
state ( node ) = Running
submit ( node )
post {
readyInv ( node )
assert ( reverse contains node )
assert ( running ( node ) )
}
}
/* * Enters the given node into the system. */
def register [ T ] ( node : A [ T ] ) : Node [ A , T ] =
{
state ( node ) = Pending
reverse ( node ) = Seq ( )
viewCache . getOrUpdate ( node , view ( node ) )
}
/* * Send the work for this node to the provided Strategy. */
def submit [ T ] ( node : A [ T ] ) ( implicit strategy : Strategy )
{
2010-08-14 15:45:26 +02:00
val v = viewCache ( node )
2010-08-22 04:55:42 +02:00
val rs = v . mixedIn transform results
2010-06-10 14:17:51 +02:00
val ud = v . uniformIn . map ( results . apply [ v . Uniform ] )
2010-06-07 14:53:21 +02:00
strategy . submit ( node , ( ) => work ( node , v . work ( rs , ud ) ) )
}
/* * Evaluates the computation 'f' for 'node'.
* This returns a Completed instance , which contains the post - processing to perform after the result is retrieved from the Strategy . */
def work [ T ] ( node : A [ T ] , f : => Either [ A [ T ] , T ] ) ( implicit strategy : Strategy ) : Completed =
{
2010-06-11 03:25:37 +02:00
val result = wideConvert ( f ) . left . map {
2011-03-21 03:54:01 +01:00
case i : Incomplete => if ( i . node . isEmpty ) i . copy ( node = Some ( node ) ) else i
case e => Incomplete ( Some ( node ) , Incomplete . Error , directCause = Some ( e ) )
2010-06-11 03:25:37 +02:00
}
2010-06-07 14:53:21 +02:00
completed {
result match {
case Left ( i ) => retire ( node , Inc ( i ) )
case Right ( Right ( v ) ) => retire ( node , Value ( v ) )
case Right ( Left ( target ) ) => call ( node , target )
}
}
}
def remove [ K , V ] ( map : Map [ K , V ] , k : K ) : V = map . remove ( k ) . getOrElse ( error ( "Key '" + k + "' not in map :\n" + map ) )
def addReverse ( node : A [ _ ] , dependent : A [ _ ] ) : Unit = reverse ( node ) ++= Seq ( dependent )
def addCaller [ T ] ( caller : A [ T ] , target : A [ T ] ) : Unit = callers . getOrUpdate ( target , IDSet . create [ A [ T ] ] ) += caller
2010-08-14 15:45:26 +02:00
def dependencies ( node : A [ _ ] ) : Iterable [ A [ _ ] ] = dependencies ( viewCache ( node ) )
2010-06-10 14:17:51 +02:00
def dependencies ( v : Node [ A , _ ] ) : Iterable [ A [ _ ] ] = v . uniformIn ++ v . mixedIn . toList
2010-06-07 14:53:21 +02:00
2011-04-23 17:49:58 +02:00
def runBefore ( node : A [ _ ] ) : Seq [ A [ _ ] ] = getSeq ( triggers . runBefore , node )
def triggeredBy ( node : A [ _ ] ) : Seq [ A [ _ ] ] = getSeq ( triggers . injectFor , node )
def getSeq ( map : collection.Map [ A [ _ ] , Seq [ A [ _ ] ] ] , node : A [ _ ] ) : Seq [ A [ _ ] ] = map . getOrElse ( node , Nil )
2010-06-07 14:53:21 +02:00
// Contracts
def addedInv ( node : A [ _ ] ) : Unit = topologicalSort ( node ) foreach addedCheck
def addedCheck ( node : A [ _ ] )
{
assert ( added ( node ) , "Not added: " + node )
assert ( viewCache contains node , "Not in view cache: " + node )
dependencyCheck ( node )
}
def dependencyCheck ( node : A [ _ ] )
{
dependencies ( node ) foreach { dep =>
def onOpt [ T ] ( o : Option [ T ] ) ( f : T => Boolean ) = o match { case None => false ; case Some ( x ) => f ( x ) }
def checkForward = onOpt ( forward . get ( node ) ) { _ contains dep }
2011-05-16 01:43:06 +02:00
def checkReverse = onOpt ( reverse . get ( dep ) ) { _ . exists ( _ == node ) }
2010-06-07 14:53:21 +02:00
assert ( done ( dep ) ^ ( checkForward && checkReverse ) )
}
}
def pendingInv ( node : A [ _ ] )
{
assert ( atState ( node , Pending ) )
2011-04-23 17:49:58 +02:00
assert ( ( dependencies ( node ) ++ runBefore ( node ) ) exists notDone )
2010-06-07 14:53:21 +02:00
}
def runningInv ( node : A [ _ ] )
{
assert ( dependencies ( node ) forall done )
assert ( ! ( forward contains node ) )
}
def newPre ( node : A [ _ ] )
{
isNew ( node )
assert ( ! ( reverse contains node ) )
assert ( ! ( forward contains node ) )
assert ( ! ( callers contains node ) )
assert ( ! ( viewCache contains node ) )
assert ( ! ( results contains node ) )
}
def topologicalSort ( node : A [ _ ] ) : Seq [ A [ _ ] ] =
{
val seen = IDSet . create [ A [ _ ] ]
def visit ( n : A [ _ ] ) : List [ A [ _ ] ] =
( seen process n ) ( List [ A [ _ ] ] ( ) ) {
node : : ( List [ A [ _ ] ] ( ) /: dependencies ( n ) ) { ( ss , dep ) => visit ( dep ) : :: ss }
}
visit ( node ) . reverse
}
def readyInv ( node : A [ _ ] )
{
assert ( dependencies ( node ) forall done )
assert ( ! ( forward contains node ) )
}
// cyclic reference checking
2011-04-08 04:51:25 +02:00
def snapshotCycleCheck ( ) : Unit =
for ( ( called : A [ c ] , callers ) <- callers . toSeq ; caller <- callers )
cycleCheck ( caller . asInstanceOf [ A [ c ] ] , called )
2010-06-07 14:53:21 +02:00
def cycleCheck [ T ] ( node : A [ T ] , target : A [ T ] )
{
if ( node eq target ) cyclic ( node , target , "Cannot call self" )
val all = IDSet . create [ A [ T ] ]
def allCallers ( n : A [ T ] ) : Unit = ( all process n ) ( ( ) ) { callers . get ( n ) . flatten . foreach ( allCallers ) }
allCallers ( node )
if ( all contains target ) cyclic ( node , target , "Cyclic reference" )
}
2011-03-21 03:54:01 +01:00
def cyclic [ T ] ( caller : A [ T ] , target : A [ T ] , msg : String ) = throw new Incomplete ( Some ( caller ) , message = Some ( msg ) , directCause = Some ( new CyclicException ( caller , target , msg ) ) )
2010-06-07 14:53:21 +02:00
final class CyclicException [ T ] ( val caller : A [ T ] , val target : A [ T ] , msg : String ) extends Exception ( msg )
// state testing
def pending ( d : A [ _ ] ) = atState ( d , Pending )
def running ( d : A [ _ ] ) = atState ( d , Running )
def calling ( d : A [ _ ] ) = atState ( d , Calling )
def done ( d : A [ _ ] ) = atState ( d , Done )
def notDone ( d : A [ _ ] ) = ! done ( d )
def atState ( d : A [ _ ] , s : State ) = state . get ( d ) == Some ( s )
def isNew ( d : A [ _ ] ) = ! added ( d )
def added ( d : A [ _ ] ) = state contains d
def complete = state . values . forall ( _ == Done )
import scala.annotation.elidable
import elidable._
@elidable ( ASSERTION ) def pre ( f : => Unit ) = f
@elidable ( ASSERTION ) def post ( f : => Unit ) = f
}