From e8673866b79f7473391dcee26243eee80d5d3cb6 Mon Sep 17 00:00:00 2001 From: Christopher Vogt Date: Thu, 9 Feb 2017 21:20:11 -0500 Subject: idempotent change propagation using lastModified instead of a non-idempotent needsUpdate flag this fixes a bug where dependees would not be rebuilt if cbt exited or was killed after dependencies were already rebuilt. --- stage1/CbtPaths.scala | 2 + stage1/ClassLoaderCache.scala | 24 --------- stage1/ContextImplementation.scala | 40 +++++++++------ stage1/MavenRepository.scala | 6 +-- stage1/Stage1.scala | 100 ++++++++++++++++++------------------ stage1/Stage1Lib.scala | 81 +++++++++++++++-------------- stage1/cbt.scala | 23 +++++---- stage1/resolver.scala | 102 +++++++++++++++++-------------------- 8 files changed, 183 insertions(+), 195 deletions(-) delete mode 100644 stage1/ClassLoaderCache.scala (limited to 'stage1') diff --git a/stage1/CbtPaths.scala b/stage1/CbtPaths.scala index 71c2ef1..c8f2279 100644 --- a/stage1/CbtPaths.scala +++ b/stage1/CbtPaths.scala @@ -9,7 +9,9 @@ case class CbtPaths(private val cbtHome: File, private val cache: File){ private val target = NailgunLauncher.TARGET.stripSuffix("/") val stage1Target: File = stage1 ++ ("/" ++ target) val stage2Target: File = stage2 ++ ("/" ++ target) + val stage1StatusFile: File = stage1Target ++ ".last-success" val stage2StatusFile: File = stage2Target ++ ".last-success" val compatibility: File = cbtHome ++ "/compatibility" val nailgunTarget: File = nailgun ++ ("/" ++ target) + val nailgunStatusFile: File = nailgunTarget ++ ".last-success" } diff --git a/stage1/ClassLoaderCache.scala b/stage1/ClassLoaderCache.scala deleted file mode 100644 index af0970e..0000000 --- a/stage1/ClassLoaderCache.scala +++ /dev/null @@ -1,24 +0,0 @@ -package cbt - -import java.net._ -import java.util._ -import collection.JavaConverters._ - -case class ClassLoaderCache( - logger: Logger, - private[cbt] hashMap: java.util.Map[AnyRef,AnyRef] -){ - val cache = new KeyLockedLazyCache[ClassLoader]( hashMap, Some(logger) ) - override def toString = ( - s"ClassLoaderCache(" - ++ - hashMap.asScala.collect{ - case (key, value) if key.isInstanceOf[String] => - key.toString.split(":").mkString("\n") -> value - }.toVector.sortBy(_._1).map{ - case (key, value) => key + " -> " + hashMap.get(value) - }.mkString("\n\n","\n\n","\n\n") - ++ - ")" - ) -} diff --git a/stage1/ContextImplementation.scala b/stage1/ContextImplementation.scala index 30db597..69094b0 100644 --- a/stage1/ContextImplementation.scala +++ b/stage1/ContextImplementation.scala @@ -1,20 +1,28 @@ package cbt import java.io._ -import java.lang._ class ContextImplementation( - val projectDirectory: File, - val cwd: File, - val argsArray: Array[String], - val enabledLoggersArray: Array[String], - val startCompat: Long, - val cbtHasChangedCompat: Boolean, - val scalaVersionOrNull: String, - val persistentCache: java.util.Map[AnyRef,AnyRef], - val transientCache: java.util.Map[AnyRef,AnyRef], - val cache: File, - val cbtHome: File, - val cbtRootHome: File, - val compatibilityTarget: File, - val parentBuildOrNull: BuildInterface -) extends Context + override val projectDirectory: File, + override val cwd: File, + override val argsArray: Array[String], + override val enabledLoggersArray: Array[String], + override val start: Long, + override val cbtLastModified: Long, + override val scalaVersionOrNull: String, + override val persistentCache: java.util.Map[AnyRef,AnyRef], + override val transientCache: java.util.Map[AnyRef,AnyRef], + override val cache: File, + override val cbtHome: File, + override val cbtRootHome: File, + override val compatibilityTarget: File, + override val parentBuildOrNull: BuildInterface +) extends Context{ + @deprecated("this method is replaced by cbtLastModified","") + def cbtHasChangedCompat = true + @deprecated("this method is replaced by start","") + def startCompat = start + @deprecated("this methods is replaced by persistentCache","") + def permanentKeys = throw new IncompatibleCbtVersionException("You need to upgrade your CBT version in this module. The Context field permanentClassLoaders is no longer supported."); + @deprecated("this methods is replaced by persistentCache","") + def permanentClassLoaders = throw new IncompatibleCbtVersionException("You need to upgrade your CBT version in this module. The Context field permanentClassLoaders is no longer supported."); +} diff --git a/stage1/MavenRepository.scala b/stage1/MavenRepository.scala index 2ac7064..a8c9b51 100644 --- a/stage1/MavenRepository.scala +++ b/stage1/MavenRepository.scala @@ -2,12 +2,12 @@ package cbt import java.io._ import java.net._ case class MavenResolver( - cbtHasChanged: Boolean, mavenCache: File, urls: URL* + cbtLastModified: Long, mavenCache: File, urls: URL* )( implicit logger: Logger, transientCache: java.util.Map[AnyRef,AnyRef] ){ def bind( dependencies: MavenDependency* ): Seq[BoundMavenDependency] - = dependencies.map( BoundMavenDependency(cbtHasChanged,mavenCache,_,urls.to) ).to + = dependencies.map( BoundMavenDependency(cbtLastModified,mavenCache,_,urls.to) ).to def bindOne( dependency: MavenDependency ): BoundMavenDependency - = BoundMavenDependency( cbtHasChanged, mavenCache, dependency, urls.to ) + = BoundMavenDependency( cbtLastModified, mavenCache, dependency, urls.to ) } diff --git a/stage1/Stage1.scala b/stage1/Stage1.scala index 27c6402..1fd4663 100644 --- a/stage1/Stage1.scala +++ b/stage1/Stage1.scala @@ -39,7 +39,8 @@ abstract class Stage2Base{ case class Stage2Args( cwd: File, args: Seq[String], - cbtHasChanged: Boolean, + stage2LastModified: Long, + logger: Logger, classLoaderCache: ClassLoaderCache, cache: File, cbtHome: File, @@ -47,8 +48,9 @@ case class Stage2Args( )( implicit val transientCache: java.util.Map[AnyRef,AnyRef] ){ - val ClassLoaderCache( logger, persistentCache ) = classLoaderCache + val persistentCache = classLoaderCache.hashMap } + object Stage1{ protected def newerThan( a: File, b: File ) ={ a.lastModified > b.lastModified @@ -56,13 +58,14 @@ object Stage1{ def getBuild( _context: java.lang.Object, buildStage1: BuildStage1Result ) = { val context = _context.asInstanceOf[Context] - val logger = new Logger( context.enabledLoggers, context.start ) - val (changed, classLoader) = buildStage2( + val logger = new Logger( context.enabledLoggers, buildStage1.start ) + val (cbtLastModified, classLoader) = buildStage2( buildStage1, - ClassLoaderCache( logger, context.persistentCache ), + new ClassLoaderCache( context.persistentCache ), context.cbtHome, - context.cache - )( context.transientCache ) + context.cache, + logger + )(context.transientCache) classLoader .loadClass("cbt.Stage2") @@ -70,15 +73,16 @@ object Stage1{ .invoke( null, context.copy( - cbtHasChanged = context.cbtHasChanged || buildStage1.changed || changed // might be redundant + cbtLastModified = Math.max( context.cbtLastModified, cbtLastModified ) ) ) } def buildStage2( - buildStage1: BuildStage1Result, classLoaderCache: ClassLoaderCache, cbtHome: File, cache: File - )(implicit transientCache: java.util.Map[AnyRef,AnyRef]): (Boolean, ClassLoader) = { - import classLoaderCache.logger + buildStage1: BuildStage1Result, classLoaderCache: ClassLoaderCache, cbtHome: File, cache: File, logger: Logger + )(implicit transientCache: java.util.Map[AnyRef,AnyRef]): (Long, ClassLoader) = { + + import buildStage1._ val lib = new Stage1Lib(logger) import lib._ @@ -89,61 +93,58 @@ object Stage1{ stage2.listFiles ++ (stage2 ++ "/plugins").listFiles ).toVector.filter(_.isFile).filter(_.toString.endsWith(".scala")) - val cbtHasChanged = buildStage1.changed || lib.needsUpdate(stage2sourceFiles, stage2StatusFile) - val cls = this.getClass.getClassLoader.loadClass("cbt.NailgunLauncher") - val cbtDependency = new CbtDependency(cbtHasChanged, mavenCache, nailgunTarget, stage1Target, stage2Target, new File(buildStage1.compatibilityClasspath)) + def cbtDependencies = new CbtDependencies( + mavenCache, nailgunTarget, stage1Target, stage2Target, + new File(buildStage1.compatibilityClasspath) + ) logger.stage1("Compiling stage2 if necessary") - compile( - cbtHasChanged, - cbtHasChanged, + val Some( stage2LastModified ) = compile( + buildStage1.stage1LastModified, stage2sourceFiles, stage2Target, stage2StatusFile, - cbtDependency.dependencies, + cbtDependencies.stage2Dependency.dependencies, mavenCache, Seq("-deprecation","-feature","-unchecked"), classLoaderCache, zincVersion = constants.zincVersion, scalaVersion = constants.scalaVersion )(transientCache) logger.stage1(s"calling CbtDependency.classLoader") - if( cbtHasChanged && classLoaderCache.cache.containsKey( cbtDependency.classpath.string ) ) { - classLoaderCache.cache.remove( cbtDependency.classpath.string ) - } else { - assert( - buildStage1.compatibilityClasspath === cbtDependency.stage1Dependency.compatibilityDependency.classpath.string, - "compatibility classpath different from NailgunLauncher" - ) - assert( - buildStage1.stage1Classpath === cbtDependency.stage1Dependency.classpath.string, - "stage1 classpath different from NailgunLauncher" - ) - assert( - classLoaderCache.cache.containsKey( cbtDependency.stage1Dependency.compatibilityDependency.classpath.string ), - "cbt unchanged, expected compatibility classloader to be cached" - ) - assert( - classLoaderCache.cache.containsKey( cbtDependency.stage1Dependency.classpath.string ), - "cbt unchanged, expected stage1/nailgun classloader to be cached" - ) - } - val stage2ClassLoader = cbtDependency.classLoader(classLoaderCache) + assert( + buildStage1.compatibilityClasspath === cbtDependencies.compatibilityDependency.classpath.string, + "compatibility classpath different from NailgunLauncher" + ) + assert( + buildStage1.stage1Classpath === cbtDependencies.stage1Dependency.classpath.string, + "stage1 classpath different from NailgunLauncher" + ) + assert( + classLoaderCache.containsKey( cbtDependencies.compatibilityDependency.classpath.string, cbtDependencies.compatibilityDependency.lastModified ), + "cbt unchanged, expected compatibility classloader to be cached" + ) + assert( + classLoaderCache.containsKey( cbtDependencies.stage1Dependency.classpath.string, cbtDependencies.stage1Dependency.lastModified ), + "cbt unchanged, expected stage1 classloader to be cached" + ) + + val stage2ClassLoader = cbtDependencies.stage2Dependency.classLoader(classLoaderCache) { // a few classloader sanity checks val compatibilityClassLoader = - cbtDependency.stage1Dependency.compatibilityDependency.classLoader(classLoaderCache) + cbtDependencies.compatibilityDependency.classLoader(classLoaderCache) assert( classOf[BuildInterface].getClassLoader == compatibilityClassLoader, classOf[BuildInterface].getClassLoader.toString ++ "\n\nis not the same as\n\n" ++ compatibilityClassLoader.toString ) //------------- val stage1ClassLoader = - cbtDependency.stage1Dependency.classLoader(classLoaderCache) + cbtDependencies.stage1Dependency.classLoader(classLoaderCache) assert( - classOf[Stage1Dependency].getClassLoader == stage1ClassLoader, - classOf[Stage1Dependency].getClassLoader.toString ++ "\n\nis not the same as\n\n" ++ stage1ClassLoader.toString + classOf[Stage1ArgsParser].getClassLoader == stage1ClassLoader, + classOf[Stage1ArgsParser].getClassLoader.toString ++ "\n\nis not the same as\n\n" ++ stage1ClassLoader.toString ) //------------- assert( @@ -152,7 +153,7 @@ object Stage1{ ) } - ( cbtHasChanged, stage2ClassLoader ) + ( stage2LastModified, stage2ClassLoader ) } def run( @@ -160,24 +161,23 @@ object Stage1{ cache: File, cbtHome: File, buildStage1: BuildStage1Result, - start: java.lang.Long, persistentCache: java.util.Map[AnyRef,AnyRef] ): Int = { val args = Stage1ArgsParser(_args.toVector) - val logger = new Logger(args.enabledLoggers, start) + val logger = new Logger(args.enabledLoggers, buildStage1.start) implicit val transientCache: java.util.Map[AnyRef,AnyRef] = new java.util.HashMap logger.stage1(s"Stage1 start") - val classLoaderCache = ClassLoaderCache( logger, persistentCache ) - + val classLoaderCache = new ClassLoaderCache( persistentCache ) - val (cbtHasChanged, classLoader) = buildStage2( buildStage1, classLoaderCache, cbtHome, cache ) + val (stage2LastModified, classLoader) = buildStage2( buildStage1, classLoaderCache, cbtHome, cache, logger ) val stage2Args = Stage2Args( new File( args.args(0) ), args.args.drop(1).dropWhile(_ == "direct").toVector, // launcher changes cause entire nailgun restart, so no need for them here - cbtHasChanged = cbtHasChanged, + stage2LastModified = stage2LastModified, + logger = logger, classLoaderCache = classLoaderCache, cache, cbtHome, diff --git a/stage1/Stage1Lib.scala b/stage1/Stage1Lib.scala index f0bb588..ad4b2d0 100644 --- a/stage1/Stage1Lib.scala +++ b/stage1/Stage1Lib.scala @@ -32,7 +32,7 @@ class BaseLib{ def realpath(name: File) = new File(java.nio.file.Paths.get(name.getAbsolutePath).normalize.toString) } -class Stage1Lib( val logger: Logger ) extends BaseLib{ +class Stage1Lib( logger: Logger ) extends BaseLib{ lib => implicit val implicitLogger: Logger = logger @@ -183,15 +183,10 @@ class Stage1Lib( val logger: Logger ) extends BaseLib{ } } - def needsUpdate( sourceFiles: Seq[File], statusFile: File ) = { - val lastCompile = statusFile.lastModified - sourceFiles.filter(_.lastModified > lastCompile).nonEmpty - } def compile( - cbtHasChanged: Boolean, - needsRecompile: Boolean, - files: Seq[File], + cbtLastModified: Long, + sourceFiles: Seq[File], compileTarget: File, statusFile: File, dependencies: Seq[Dependency], @@ -202,20 +197,23 @@ class Stage1Lib( val logger: Logger ) extends BaseLib{ scalaVersion: String )( implicit transientCache: java.util.Map[AnyRef, AnyRef] - ): Option[File] = { - val classpath = Dependencies(dependencies).classpath + ): Option[Long] = { + val d = Dependencies(dependencies) + val classpath = d.classpath val cp = classpath.string if(classpath.files.isEmpty) - throw new Exception("Trying to compile with empty classpath. Source files: " ++ files.toString) + throw new Exception("Trying to compile with empty classpath. Source files: " ++ sourceFiles.toString) - if( files.isEmpty ){ + if( sourceFiles.isEmpty ){ None }else{ - if( needsRecompile ){ - def Resolver(urls: URL*) = MavenResolver(cbtHasChanged, mavenCache, urls: _*) + val start = System.currentTimeMillis + val lastCompiled = statusFile.lastModified + if( d.lastModified > lastCompiled || sourceFiles.exists(_.lastModified > lastCompiled) ){ + def Resolver(urls: URL*) = MavenResolver(cbtLastModified, mavenCache, urls: _*) val zinc = Resolver(mavenCentral).bindOne(MavenDependency("com.typesafe.zinc","zinc", zincVersion)) val zincDeps = zinc.transitiveDependencies - + val sbtInterface = zincDeps .collect{ case d @ @@ -242,8 +240,6 @@ class Stage1Lib( val logger: Logger ) extends BaseLib{ val scalaReflect = Resolver(mavenCentral).bindOne(MavenDependency("org.scala-lang","scala-reflect",scalaVersion)).jar val scalaCompiler = Resolver(mavenCentral).bindOne(MavenDependency("org.scala-lang","scala-compiler",scalaVersion)).jar - val start = System.currentTimeMillis - val _class = "com.typesafe.zinc.Main" val dualArgs = Seq( @@ -264,7 +260,7 @@ class Stage1Lib( val logger: Logger ) extends BaseLib{ _class, dualArgs ++ singleArgs ++ Seq( "-cp", cp // let's put cp last. It so long - ) ++ files.map(_.toString), + ) ++ sourceFiles.map(_.toString), zinc.classLoader(classLoaderCache) ) } catch { @@ -283,7 +279,7 @@ class Stage1Lib( val logger: Logger ) extends BaseLib{ -cp \\ ${classpath.strings.mkString(":\\\n")} \\ \\ - ${files.sorted.mkString(" \\\n")} + ${sourceFiles.sorted.mkString(" \\\n")} """ ) @@ -300,8 +296,10 @@ class Stage1Lib( val logger: Logger ) extends BaseLib{ } else { System.exit(code.integer) // FIXME: let's find a better solution for error handling. Maybe a monad after all. } + Some( start ) + } else { + Some( lastCompiled ) } - Some( compileTarget ) } } def redirectOutToErr[T](code: => T): T = { @@ -360,11 +358,11 @@ class Stage1Lib( val logger: Logger ) extends BaseLib{ ) def cacheOnDisk[T] - ( cbtHasChanged: Boolean, cacheFile: File ) + ( cbtLastModified: Long, cacheFile: File ) ( deserialize: String => T ) ( serialize: T => String ) ( compute: => Seq[T] ) = { - if(!cbtHasChanged && cacheFile.exists){ + if(cacheFile.exists && cacheFile.lastModified > cbtLastModified ){ import collection.JavaConversions._ Files .readAllLines( cacheFile.toPath, StandardCharsets.UTF_8 ) @@ -380,7 +378,7 @@ class Stage1Lib( val logger: Logger ) extends BaseLib{ def dependencyTreeRecursion(root: Dependency, indent: Int = 0): String = ( ( " " * indent ) - ++ (if(root.needsUpdate) red(root.show) else root.show) + ++ root.show // (if(root.needsUpdate) red(root.show) else root.show) ++ root.dependencies.map( d => "\n" ++ dependencyTreeRecursion(d,indent + 1) ).mkString @@ -420,33 +418,40 @@ class Stage1Lib( val logger: Logger ) extends BaseLib{ def classLoaderRecursion( dependency: Dependency, latest: Map[(String,String),Dependency], cache: ClassLoaderCache ): ClassLoader = { // FIXME: shouldn't we be using KeyLockedLazyCache instead of hashmap directly here? - val d = dependency val dependencies = dependency.dependencies - def dependencyClassLoader( latest: Map[(String,String),Dependency], cache: ClassLoaderCache ): ClassLoader = { + val dependencyClassLoader: ClassLoader = { if( dependency.dependencies.isEmpty ){ // wrap for caching new cbt.URLClassLoader( ClassPath(), ClassLoader.getSystemClassLoader().getParent() ) } else if( dependencies.size == 1 ){ classLoaderRecursion( dependencies.head, latest, cache ) } else{ - val cp = d.dependencyClasspath.string - if( dependencies.exists(_.needsUpdate) && cache.cache.containsKey(cp) ){ - cache.cache.remove(cp) - } - def cl = new MultiClassLoader( dependencies.map( classLoaderRecursion(_, latest, cache) ) ) - if(d.isInstanceOf[BuildInterface]) + val lastModified = dependencies.map( _.lastModified ).max + val cp = dependency.dependencyClasspath.string + val cl = + new MultiClassLoader( + dependencies.map( classLoaderRecursion(_, latest, cache) ) + ) + if(dependency.isInstanceOf[BuildInterface]) cl // Don't cache builds right now. We need to fix invalidation first. - else - cache.cache.get( cp, cl ) + else{ + if( !cache.containsKey( cp, lastModified ) ){ + cache.put( cp, cl, lastModified ) + } + cache.get( cp, lastModified ) + } } } val a = actual( dependency, latest ) - def cl = new cbt.URLClassLoader( a.exportedClasspath, dependencyClassLoader(latest, cache) ) - if(d.isInstanceOf[BuildInterface]) - cl - else - cache.cache.get( a.classpath.string, cl ).asInstanceOf[ClassLoader] + def cl = new cbt.URLClassLoader( a.exportedClasspath, dependencyClassLoader ) + + val cp = a.classpath.string + val lastModified = a.lastModified + if( !cache.containsKey( cp, lastModified ) ){ + cache.put( cp, cl, lastModified ) + } + cache.get( cp, lastModified ) } } diff --git a/stage1/cbt.scala b/stage1/cbt.scala index 01c9303..0b0ccbf 100644 --- a/stage1/cbt.scala +++ b/stage1/cbt.scala @@ -33,6 +33,11 @@ object `package`{ def ++( s: String ): URL = new URL( url.toString ++ s ) def string = url.toString } + implicit class SeqExtensions[T](seq: Seq[T]){ + def maxOption(implicit ev: Ordering[T]): Option[T] = try{ Some(seq.max) } catch { + case e:java.lang.UnsupportedOperationException if e.getMessage === "empty.max" => None + } + } implicit class BuildInterfaceExtensions(build: BuildInterface){ import build._ // TODO: if every build has a method triggers a callback if files change @@ -52,30 +57,30 @@ object `package`{ def exportedClasspath: ClassPath = ClassPath(exportedClasspathArray.to) def classpath = exportedClasspath ++ dependencyClasspath def dependencies: Seq[Dependency] = dependenciesArray.to - def needsUpdate: Boolean = needsUpdateCompat } implicit class ContextExtensions(subject: Context){ import subject._ val paths = CbtPaths(cbtHome, cache) implicit def logger: Logger = new Logger(enabledLoggers, start) - def classLoaderCache: ClassLoaderCache = new ClassLoaderCache( logger, persistentCache ) - def cbtDependency = { + def classLoaderCache: ClassLoaderCache = new ClassLoaderCache( persistentCache ) + def cbtDependencies = { import paths._ - new CbtDependency(cbtHasChanged, mavenCache, nailgunTarget, stage1Target, stage2Target, compatibilityTarget)(logger, transientCache) + new CbtDependencies(mavenCache, nailgunTarget, stage1Target, stage2Target, compatibilityTarget)(logger, transientCache) } + val cbtDependency = cbtDependencies.stage2Dependency + def args: Seq[String] = argsArray.to def enabledLoggers: Set[String] = enabledLoggersArray.to def scalaVersion = Option(scalaVersionOrNull) def parentBuild = Option(parentBuildOrNull) - def start: scala.Long = startCompat - def cbtHasChanged: scala.Boolean = cbtHasChangedCompat + def cbtLastModified: scala.Long = subject.cbtLastModified def copy( projectDirectory: File = projectDirectory, args: Seq[String] = args, //enabledLoggers: Set[String] = enabledLoggers, - cbtHasChanged: Boolean = cbtHasChanged, + cbtLastModified: Long = cbtLastModified, scalaVersion: Option[String] = scalaVersion, cbtHome: File = cbtHome, parentBuild: Option[BuildInterface] = None @@ -84,8 +89,8 @@ object `package`{ cwd, args.to, enabledLoggers.to, - startCompat, - cbtHasChangedCompat, + start, + cbtLastModified, scalaVersion.getOrElse(null), persistentCache, transientCache, diff --git a/stage1/resolver.scala b/stage1/resolver.scala index 8e46135..4a39d14 100644 --- a/stage1/resolver.scala +++ b/stage1/resolver.scala @@ -18,22 +18,14 @@ trait DependencyImplementation extends Dependency{ */ protected lazy val taskCache = new PerClassCache(transientCache, moduleKey) - /** - CAREFUL: this is never allowed to return true for the same dependency more than - once in a single cbt run. Otherwise we can end up with multiple classloaders - for the same classes since classLoaderRecursion recreates the classLoader when it - sees this flag and it can be called multiple times. Maybe we can find a safer - solution than this current state. - */ - def needsUpdate: Boolean - //def cacheClassLoader: Boolean = false private[cbt] def targetClasspath: ClassPath def dependencyClasspathArray: Array[File] = dependencyClasspath.files.toArray def exportedClasspathArray: Array[File] = exportedClasspath.files.toArray def exportedClasspath: ClassPath def dependenciesArray: Array[Dependency] = dependencies.to - def needsUpdateCompat: java.lang.Boolean = needsUpdate + @deprecated("this method is replaced by lastModifiedCompat","") + def needsUpdateCompat = true /* //private type BuildCache = KeyLockedLazyCache[Dependency, Future[ClassPath]] @@ -122,63 +114,63 @@ trait DependencyImplementation extends Dependency{ } // TODO: all this hard codes the scala version, needs more flexibility -class ScalaCompilerDependency(cbtHasChanged: Boolean, mavenCache: File, version: String)(implicit logger: Logger, transientCache: java.util.Map[AnyRef,AnyRef]) extends BoundMavenDependency(cbtHasChanged, mavenCache, MavenDependency("org.scala-lang","scala-compiler",version, Classifier.none), Seq(mavenCentral)) -class ScalaLibraryDependency (cbtHasChanged: Boolean, mavenCache: File, version: String)(implicit logger: Logger, transientCache: java.util.Map[AnyRef,AnyRef]) extends BoundMavenDependency(cbtHasChanged, mavenCache, MavenDependency("org.scala-lang","scala-library",version, Classifier.none), Seq(mavenCentral)) -class ScalaReflectDependency (cbtHasChanged: Boolean, mavenCache: File, version: String)(implicit logger: Logger, transientCache: java.util.Map[AnyRef,AnyRef]) extends BoundMavenDependency(cbtHasChanged, mavenCache, MavenDependency("org.scala-lang","scala-reflect",version, Classifier.none), Seq(mavenCentral)) +class ScalaCompilerDependency(cbtLastModified: Long, mavenCache: File, version: String)(implicit logger: Logger, transientCache: java.util.Map[AnyRef,AnyRef]) extends BoundMavenDependency(cbtLastModified, mavenCache, MavenDependency("org.scala-lang","scala-compiler",version, Classifier.none), Seq(mavenCentral)) +class ScalaLibraryDependency (cbtLastModified: Long, mavenCache: File, version: String)(implicit logger: Logger, transientCache: java.util.Map[AnyRef,AnyRef]) extends BoundMavenDependency(cbtLastModified, mavenCache, MavenDependency("org.scala-lang","scala-library",version, Classifier.none), Seq(mavenCentral)) +class ScalaReflectDependency (cbtLastModified: Long, mavenCache: File, version: String)(implicit logger: Logger, transientCache: java.util.Map[AnyRef,AnyRef]) extends BoundMavenDependency(cbtLastModified, mavenCache, MavenDependency("org.scala-lang","scala-reflect",version, Classifier.none), Seq(mavenCentral)) -class ScalaDependencies(cbtHasChanged: Boolean, mavenCache: File, version: String)(implicit logger: Logger, transientCache: java.util.Map[AnyRef,AnyRef]) extends Dependencies( +class ScalaDependencies(cbtLastModified: Long, mavenCache: File, version: String)(implicit logger: Logger, transientCache: java.util.Map[AnyRef,AnyRef]) extends Dependencies( Seq( - new ScalaCompilerDependency(cbtHasChanged, mavenCache, version), - new ScalaLibraryDependency(cbtHasChanged, mavenCache, version), - new ScalaReflectDependency(cbtHasChanged, mavenCache, version) + new ScalaCompilerDependency(cbtLastModified, mavenCache, version), + new ScalaLibraryDependency(cbtLastModified, mavenCache, version), + new ScalaReflectDependency(cbtLastModified, mavenCache, version) ) ) case class BinaryDependency( paths: Seq[File], dependencies: Seq[Dependency] )(implicit val logger: Logger, val transientCache: java.util.Map[AnyRef,AnyRef]) extends DependencyImplementation{ assert(paths.nonEmpty) def exportedClasspath = ClassPath(paths) - override def needsUpdate = false + override def lastModified = paths.map(_.lastModified).maxOption.getOrElse(0) // FIXME: cache this def targetClasspath = exportedClasspath def moduleKey = this.getClass.getName ++ "(" ++ paths.mkString(", ") ++ ")" } /** Allows to easily assemble a bunch of dependencies */ case class Dependencies( dependencies: Seq[Dependency] )(implicit val logger: Logger, val transientCache: java.util.Map[AnyRef,AnyRef]) extends DependencyImplementation{ - override def needsUpdate = dependencies.exists(_.needsUpdate) - override def exportedClasspath = ClassPath() - override def targetClasspath = ClassPath() + override def lastModified = dependencies.map(_.lastModified).maxOption.getOrElse(0) def moduleKey = this.getClass.getName ++ "(" ++ dependencies.map(_.moduleKey).mkString(", ") ++ ")" + def targetClasspath = ClassPath() + def exportedClasspath = ClassPath() } -class Stage1Dependency(cbtHasChanged: Boolean, mavenCache: File, nailgunTarget: File, stage1Target: File, compatibilityTarget: File)(implicit logger: Logger, transientCache: java.util.Map[AnyRef,AnyRef]) extends BinaryDependency( - Seq(nailgunTarget, stage1Target), - Seq( - new CompatibilityDependency(cbtHasChanged, compatibilityTarget) - ) ++ - MavenResolver(cbtHasChanged,mavenCache,mavenCentral).bind( - MavenDependency("org.scala-lang","scala-library",constants.scalaVersion), - MavenDependency("org.scala-lang.modules","scala-xml_"+constants.scalaMajorVersion,constants.scalaXmlVersion) - ) -){ - val compatibilityDependency = new CompatibilityDependency(cbtHasChanged, compatibilityTarget) - +case class PostBuildDependency(target: File, _dependencies: Seq[DependencyImplementation])(implicit val logger: Logger, val transientCache: java.util.Map[AnyRef,AnyRef]) extends DependencyImplementation{ + override final lazy val lastModified = (target++".last-success").lastModified + def moduleKey = target.string + override def targetClasspath = exportedClasspath + override def exportedClasspath = ClassPath( Seq(target) ) + override def dependencies = _dependencies } - -class CompatibilityDependency(cbtHasChanged: Boolean, compatibilityTarget: File)(implicit logger: Logger, transientCache: java.util.Map[AnyRef,AnyRef]) extends BinaryDependency( - Seq(compatibilityTarget), Nil -) - -class CbtDependency(cbtHasChanged: Boolean, mavenCache: File, nailgunTarget: File, stage1Target: File, stage2Target: File, compatibilityTarget: File)(implicit logger: Logger, transientCache: java.util.Map[AnyRef,AnyRef]) extends BinaryDependency( - Seq( stage2Target ), - Seq( - new Stage1Dependency(cbtHasChanged, mavenCache, nailgunTarget, stage1Target, compatibilityTarget) - ) ++ - MavenResolver(cbtHasChanged, mavenCache,mavenCentral).bind( - MavenDependency("net.incongru.watchservice","barbary-watchservice","1.0"), - MavenDependency("org.eclipse.jgit", "org.eclipse.jgit", "4.2.0.201601211800-r") +case class CbtDependencies(mavenCache: File, nailgunTarget: File, stage1Target: File, stage2Target: File, compatibilityTarget: File)(implicit logger: Logger, val transientCache: java.util.Map[AnyRef,AnyRef]){ + val compatibilityDependency = PostBuildDependency(compatibilityTarget, Nil) + val cbtLastModified = (stage2Target++".last-success").lastModified + val stage1Dependency = PostBuildDependency( + stage1Target, + Seq( + PostBuildDependency(nailgunTarget, Nil), + compatibilityDependency + ) ++ + MavenResolver(cbtLastModified,mavenCache,mavenCentral).bind( + MavenDependency("org.scala-lang","scala-library",constants.scalaVersion), + MavenDependency("org.scala-lang.modules","scala-xml_"+constants.scalaMajorVersion,constants.scalaXmlVersion) + ) + ) + val stage2Dependency = PostBuildDependency( + stage2Target, + stage1Dependency +: + MavenResolver(cbtLastModified, mavenCache,mavenCentral).bind( + MavenDependency("net.incongru.watchservice","barbary-watchservice","1.0"), + MavenDependency("org.eclipse.jgit", "org.eclipse.jgit", "4.2.0.201601211800-r") + ) ) -){ - val stage1Dependency = new Stage1Dependency(cbtHasChanged, mavenCache, nailgunTarget, stage1Target, compatibilityTarget) } case class Classifier(name: Option[String]) @@ -191,11 +183,11 @@ abstract class DependenciesProxy{ } class BoundMavenDependencies( - cbtHasChanged: Boolean, mavenCache: File, urls: Seq[URL], mavenDependencies: Seq[MavenDependency] + cbtLastModified: Long, mavenCache: File, urls: Seq[URL], mavenDependencies: Seq[MavenDependency] )( implicit logger: Logger, transientCache: java.util.Map[AnyRef,AnyRef] ) extends Dependencies( - mavenDependencies.map( BoundMavenDependency(cbtHasChanged,mavenCache,_,urls) ) + mavenDependencies.map( BoundMavenDependency(cbtLastModified,mavenCache,_,urls) ) ) case class MavenDependency( groupId: String, artifactId: String, version: String, classifier: Classifier = Classifier.none @@ -209,7 +201,7 @@ object MavenDependency{ } // FIXME: take MavenResolver instead of mavenCache and repositories separately case class BoundMavenDependency( - cbtHasChanged: Boolean, mavenCache: File, mavenDependency: MavenDependency, repositories: Seq[URL] + cbtLastModified: Long, mavenCache: File, mavenDependency: MavenDependency, repositories: Seq[URL] )( implicit val logger: Logger, val transientCache: java.util.Map[AnyRef,AnyRef] ) extends ArtifactInfo with DependencyImplementation{ @@ -233,7 +225,7 @@ case class BoundMavenDependency( ) override def show: String = this.getClass.getSimpleName ++ "(" ++ mavenDependency.serialize ++ ")" - override def needsUpdate = false + override final lazy val lastModified = classpath.strings.map(new File(_).lastModified).max private val groupPath = groupId.split("\\.").mkString("/") protected[cbt] def basePath(useClassifier: Boolean) = s"/$groupPath/$artifactId/$version/$artifactId-$version" ++ (if (useClassifier) classifier.name.map("-"++_).getOrElse("") else "") @@ -276,7 +268,7 @@ case class BoundMavenDependency( (pomXml \ "parent").collect{ case parent => BoundMavenDependency( - cbtHasChanged: Boolean, + cbtLastModified: Long, mavenCache, MavenDependency( (parent \ "groupId").text, @@ -314,7 +306,7 @@ case class BoundMavenDependency( if(classifier == Classifier.sources) Seq() else { lib.cacheOnDisk( - cbtHasChanged, mavenCache ++ basePath(true) ++ ".pom.dependencies" + cbtLastModified, mavenCache ++ basePath(true) ++ ".pom.dependencies" )( MavenDependency.deserialize )( _.serialize ){ (pomXml \ "dependencies" \ "dependency").collect{ case xml if ( (xml \ "scope").text == "" || (xml \ "scope").text == "compile" ) && (xml \ "optional").text != "true" => @@ -339,7 +331,7 @@ case class BoundMavenDependency( MavenDependency( groupId, artifactId, version, classifier ) }.toVector }.map( - BoundMavenDependency( cbtHasChanged, mavenCache, _, repositories ) + BoundMavenDependency( cbtLastModified, mavenCache, _, repositories ) ).to } } -- cgit v1.2.3