2010-01-06 01:50:43 +01:00
/* sbt -- Simple Build Tool
* Copyright 2009 , 2010 Mark Harrah
*/
2009-08-30 17:10:37 +02:00
package xsbt
import java.io.File
2010-01-06 01:50:43 +01:00
import xsbt.api. { APIFormat , SameAPI }
import xsbti.api.Source
2009-08-30 17:10:37 +02:00
2010-01-06 01:50:43 +01:00
trait CompileImpl [ R ]
{
def apply ( sourceChanges : ChangeReport [ File ] , classpathChanges : ChangeReport [ File ] , outputDirectory : File , options : Seq [ String ] ) : Task [ R ]
def tracked : Seq [ Tracked ]
}
final class Compile [ R ] ( val cacheDirectory : File , val sources : Task [ Set [ File ] ] , val classpath : Task [ Set [ File ] ] ,
val outputDirectory : Task [ File ] , val options : Task [ Seq [ String ] ] , compileImpl : CompileImpl [ R ] ) extends TrackedTaskDefinition [ R ]
2009-08-30 17:10:37 +02:00
{
2009-08-31 03:53:38 +02:00
val trackedClasspath = Difference . inputs ( classpath , FilesInfo . lastModified , cacheFile ( "classpath" ) )
val trackedSource = Difference . inputs ( sources , FilesInfo . hash , cacheFile ( "sources" ) )
2009-08-30 17:10:37 +02:00
val trackedOptions =
{
import Cache._
2009-09-05 18:19:34 +02:00
import Task._
new Changed ( ( outputDirectory , options ) map ( "-d" : : _ . getAbsolutePath :: _ . toList ) , cacheFile ( "options" ) )
2009-08-30 17:10:37 +02:00
}
2009-09-05 18:19:34 +02:00
2010-01-06 01:50:43 +01:00
val task =
2009-08-30 17:10:37 +02:00
trackedClasspath { rawClasspathChanges => // detect changes to the classpath (last modified only)
2010-01-06 01:50:43 +01:00
trackedSource { rawSourceChanges => // detect changes to sources (hash only)
2009-08-30 17:10:37 +02:00
val newOpts = ( opts : Seq [ String ] ) => ( opts , rawSourceChanges . markAllModified , rawClasspathChanges . markAllModified ) // if options changed, mark everything changed
val sameOpts = ( opts : Seq [ String ] ) => ( opts , rawSourceChanges , rawClasspathChanges )
trackedOptions ( newOpts , sameOpts ) bind { // detect changes to options
2009-09-04 05:40:47 +02:00
case ( options , sourceChanges , classpathChanges ) =>
2010-01-06 01:50:43 +01:00
outputDirectory bind { outDir =>
FileUtilities . createDirectory ( outDir )
compileImpl ( sourceChanges , classpathChanges , outDir , options )
2009-08-30 17:10:37 +02:00
}
}
}
2010-01-06 01:50:43 +01:00
} dependsOn ( sources , classpath , options , outputDirectory ) // raise these dependencies to the top for parallelism
2009-09-05 18:19:34 +02:00
2010-01-06 01:50:43 +01:00
lazy val tracked = Seq ( trackedClasspath , trackedSource , trackedOptions ) ++ compileImpl . tracked
2009-08-30 17:10:37 +02:00
}
2009-09-05 18:19:34 +02:00
2010-01-06 01:50:43 +01:00
object AggressiveCompile
{
def apply ( sources : Task [ Set [ File ] ] , classpath : Task [ Set [ File ] ] , outputDirectory : Task [ File ] , options : Task [ Seq [ String ] ] ,
cacheDirectory : File , compilerTask : Task [ AnalyzingCompiler ] , log : CompileLogger ) : Compile [ Set [ File ] ] =
2009-08-30 17:10:37 +02:00
{
2010-01-06 01:50:43 +01:00
val implCache = new File ( cacheDirectory , "deps" )
val baseCache = new File ( cacheDirectory , "inputs" )
val impl = new AggressiveCompile ( implCache , compilerTask , log )
new Compile ( baseCache , sources , classpath , outputDirectory , options , impl )
2009-09-04 05:40:47 +02:00
}
2010-01-06 01:50:43 +01:00
}
class AggressiveCompile ( val cacheDirectory : File , val compilerTask : Task [ AnalyzingCompiler ] , val log : CompileLogger ) extends CompileImpl [ Set [ File ] ]
{
def apply ( sourceChanges : ChangeReport [ File ] , classpathChanges : ChangeReport [ File ] , outputDirectory : File , options : Seq [ String ] ) : Task [ Set [ File ] ] =
compilerTask bind { compiler =>
tracking { tracker =>
2010-01-23 15:33:42 +01:00
timestamp { tstamp =>
2010-01-06 01:50:43 +01:00
Task {
log . info ( "Removed sources: \n\t" + sourceChanges . removed . mkString ( "\n\t" ) )
log . info ( "Added sources: \n\t" + sourceChanges . added . mkString ( "\n\t" ) )
log . info ( "Modified sources: \n\t" + ( sourceChanges . modified -- sourceChanges . added -- sourceChanges . removed ) . mkString ( "\n\t" ) )
val classpath = classpathChanges . checked
val readTracker = tracker . read
2010-01-23 02:17:49 +01:00
// directories that are no longer on the classpath, not necessarily removed from the filesystem
val removedDirectories = classpathChanges . removed . filter ( _ . isDirectory )
2010-01-23 15:33:42 +01:00
log . info ( "Directories no longer on classpath:\n\t" + removedDirectories . mkString ( "\n\t" ) )
2010-01-06 01:50:43 +01:00
2010-01-23 15:33:42 +01:00
def uptodate ( time : Long , files : Iterable [ File ] ) = files . forall ( _ . lastModified < time )
2010-01-23 02:17:49 +01:00
def isOutofdate ( file : File , related : => Iterable [ File ] ) = ! file . exists || ! uptodate ( file . lastModified , related )
2010-01-23 15:33:42 +01:00
def invalidatesUses ( file : File ) = ! file . exists || file . lastModified > tstamp
2010-01-23 02:17:49 +01:00
def isProductOutofdate ( product : File ) = isOutofdate ( product , readTracker . sources ( product ) )
def inRemovedDirectory ( file : File ) = removedDirectories . exists ( dir => FileUtilities . relativize ( dir , file ) . isDefined )
2010-01-23 15:33:42 +01:00
def isUsedOutofdate ( file : File ) = classpathChanges . modified ( file ) || inRemovedDirectory ( file ) || invalidatesUses ( file )
2010-01-23 02:17:49 +01:00
// these are products that no longer exist or are older than the sources that produced them
2010-01-06 01:50:43 +01:00
val outofdateProducts = readTracker . allProducts . filter ( isProductOutofdate )
2010-01-23 15:33:42 +01:00
log . info ( "Out of date products:\n\t" + outofdateProducts . mkString ( "\n\t" ) )
2010-01-23 02:17:49 +01:00
// used classes and jars that a) no longer exist b) are no longer on the classpath or c) are newer than the sources that use them
val outofdateUses = readTracker . allUsed . filter ( isUsedOutofdate )
2010-01-23 15:33:42 +01:00
log . info ( "Out of date binaries:\n\t" + outofdateUses . mkString ( "\n\t" ) )
2010-01-23 02:17:49 +01:00
val modifiedSources = sourceChanges . modified
val invalidatedByClasspath = outofdateUses . flatMap ( readTracker . usedBy )
2010-01-23 15:33:42 +01:00
log . info ( "Invalidated by classpath changes:\n\t" + invalidatedByClasspath . mkString ( "\n\t" ) )
2010-01-23 02:17:49 +01:00
val invalidatedByRemovedSrc = sourceChanges . removed . flatMap ( readTracker . dependsOn )
2010-01-23 15:33:42 +01:00
log . info ( "Invalidated by removed sources:\n\t" + invalidatedByRemovedSrc . mkString ( "\n\t" ) )
2010-01-23 02:17:49 +01:00
val productsOutofdate = outofdateProducts . flatMap ( readTracker . sources )
2010-01-23 15:33:42 +01:00
log . info ( "Invalidated by out of date products:\n\t" + productsOutofdate . mkString ( "\n\t" ) )
2010-01-06 01:50:43 +01:00
2010-01-23 02:17:49 +01:00
val rawInvalidatedSources = modifiedSources ++ invalidatedByClasspath ++ invalidatedByRemovedSrc ++ productsOutofdate
2010-01-06 01:50:43 +01:00
val invalidatedSources = scc ( readTracker , rawInvalidatedSources )
val sources = invalidatedSources . filter ( _ . exists )
val previousAPIMap = Map ( ) ++ sources . map { src => ( src , APIFormat . read ( readTracker . tag ( src ) ) ) }
val invalidatedProducts = outofdateProducts ++ products ( readTracker , invalidatedSources )
val transitiveIfNeeded = InvalidateTransitive ( tracker , sources )
tracker . removeAll ( invalidatedProducts ++ classpathChanges . modified ++ ( invalidatedSources -- sources ) )
tracker . pending ( sources )
FileUtilities . delete ( invalidatedProducts )
2010-01-23 15:33:42 +01:00
log . info ( "All initially invalidated sources:\n\t" + sources . mkString ( "\n\t" ) )
2010-01-06 01:50:43 +01:00
if ( ! sources . isEmpty )
{
val newAPIMap = doCompile ( sources , classpath , outputDirectory , options , tracker , compiler , log )
val apiChanged = sources filter { src => ! sameAPI ( previousAPIMap , newAPIMap , src ) }
log . info ( "Sources with API changes:\n\t" + apiChanged . mkString ( "\n\t" ) )
val finalAPIMap =
2010-01-28 02:39:07 +01:00
// if either nothing changed or everything was recompiled, stop here
if ( apiChanged . isEmpty || sources . size == sourceChanges . checked . size ) newAPIMap
2010-01-06 01:50:43 +01:00
else
{
2010-01-28 02:39:07 +01:00
//val changedNames = TopLevel.nameChanges(newAPIMap.values, previousAPIMap.values)
2010-01-06 01:50:43 +01:00
InvalidateTransitive . clean ( tracker , FileUtilities . delete , transitiveIfNeeded )
val sources = transitiveIfNeeded . invalid ** sourceChanges . checked
log . info ( "All sources invalidated by API changes:\n\t" + sources . mkString ( "\n\t" ) )
doCompile ( sources , classpath , outputDirectory , options , tracker , compiler , log )
}
finalAPIMap . foreach { case ( src , api ) => tracker . tag ( src , APIFormat . write ( api ) ) }
}
Set ( ) ++ tracker . read . allProducts
2009-09-05 18:19:34 +02:00
}
2010-01-23 15:33:42 +01:00
} }
2009-08-30 17:10:37 +02:00
}
2010-01-06 01:50:43 +01:00
def products ( tracker : ReadTracking [ File ] , srcs : Set [ File ] ) : Set [ File ] = srcs . flatMap ( tracker . products )
def doCompile ( sources : Set [ File ] , classpath : Set [ File ] , outputDirectory : File , options : Seq [ String ] , tracker : UpdateTracking [ File ] , compiler : AnalyzingCompiler , log : CompileLogger ) : scala.collection.Map [ File , Source ] =
2009-08-30 17:10:37 +02:00
{
2010-01-06 01:50:43 +01:00
val callback = new APIAnalysisCallback ( tracker )
log . debug ( "Compiling using compiler " + compiler )
compiler ( sources , classpath , outputDirectory , options , callback , 100 , log )
callback . apiMap
2009-08-30 17:10:37 +02:00
}
2010-01-06 01:50:43 +01:00
import sbinary.DefaultProtocol.FileFormat
val tracking = new DependencyTracked ( cacheDirectory , true , ( files : File ) => FileUtilities . delete ( files ) )
2010-01-23 15:33:42 +01:00
val timestamp = new Timestamp ( new File ( cacheDirectory , "timestamp" ) )
def tracked = Seq ( tracking , timestamp )
2010-01-06 01:50:43 +01:00
def sameAPI [ T ] ( a : scala.collection.Map [ T , Source ] , b : scala.collection.Map [ T , Source ] , t : T ) : Boolean = sameAPI ( a . get ( t ) , b . get ( t ) )
def sameAPI ( a : Option [ Source ] , b : Option [ Source ] ) : Boolean =
if ( a . isEmpty ) b . isEmpty else ( b . isDefined && SameAPI ( a . get , b . get ) )
// TODO: implement
def scc ( readTracker : ReadTracking [ File ] , sources : Set [ File ] ) = sources
2009-09-04 05:40:47 +02:00
}
2010-01-06 01:50:43 +01:00
private final class APIAnalysisCallback ( tracking : UpdateTracking [ File ] ) extends xsbti . AnalysisCallback
2009-08-30 17:10:37 +02:00
{
2010-01-06 01:50:43 +01:00
val apiMap = new scala . collection . mutable . HashMap [ File , Source ]
def sourceDependency ( dependsOn : File , source : File ) { tracking . dependency ( source , dependsOn ) }
def jarDependency ( jar : File , source : File ) { tracking . use ( source , jar ) }
def classDependency ( clazz : File , source : File ) { tracking . dependency ( source , clazz ) }
def generatedClass ( source : File , clazz : File ) { tracking . product ( source , clazz ) }
def api ( source : File , api : xsbti . api . Source ) { apiMap ( source ) = api }
def superclassNames = Array ( )
def superclassNotFound ( superclassName : String ) { }
def beginSource ( source : File ) { }
def endSource ( source : File ) { }
def foundApplication ( source : File , className : String ) { }
def foundSubclass ( source : File , subclassName : String , superclassName : String , isModule : Boolean ) { }
2009-08-30 17:10:37 +02:00
}