diff options
author | Jan Christopher Vogt <oss.nsp@cvogt.org> | 2017-02-10 01:11:22 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-02-10 01:11:22 -0500 |
commit | d743136c6b98fa91f501cc15dc47530f8f93b8fe (patch) | |
tree | aedb9af30e41f056aea148cc9e3b37d17327ca54 /stage1 | |
parent | e2cb8726735ba306ade3befefae3b87549b52c9f (diff) | |
parent | f5e653e033e468c58e8a832e0dce3a7020a66ce4 (diff) | |
download | cbt-d743136c6b98fa91f501cc15dc47530f8f93b8fe.tar.gz cbt-d743136c6b98fa91f501cc15dc47530f8f93b8fe.tar.bz2 cbt-d743136c6b98fa91f501cc15dc47530f8f93b8fe.zip |
Merge pull request #314 from cvogt/fix-update-bugs
better caching and change propagation fixing link-time errors
Diffstat (limited to 'stage1')
-rw-r--r-- | stage1/Cache.scala | 14 | ||||
-rw-r--r-- | stage1/CachingClassLoader.scala | 2 | ||||
-rw-r--r-- | stage1/CbtPaths.scala | 2 | ||||
-rw-r--r-- | stage1/ClassLoaderCache.scala | 24 | ||||
-rw-r--r-- | stage1/ContextImplementation.scala | 44 | ||||
-rw-r--r-- | stage1/KeyLockedLazyCache.scala | 61 | ||||
-rw-r--r-- | stage1/MavenRepository.scala | 14 | ||||
-rw-r--r-- | stage1/PoorMansProfiler.scala | 4 | ||||
-rw-r--r-- | stage1/Stage1.scala | 126 | ||||
-rw-r--r-- | stage1/Stage1Lib.scala | 118 | ||||
-rw-r--r-- | stage1/cbt.scala | 36 | ||||
-rw-r--r-- | stage1/logger.scala | 2 | ||||
-rw-r--r-- | stage1/resolver.scala | 165 |
13 files changed, 305 insertions, 307 deletions
diff --git a/stage1/Cache.scala b/stage1/Cache.scala deleted file mode 100644 index a8036e5..0000000 --- a/stage1/Cache.scala +++ /dev/null @@ -1,14 +0,0 @@ -package cbt -/** -Caches exactly one value. -Is there a less boiler-platy way to achieve this, that doesn't -require creating an instance for each thing you want to cache? -*/ -class Cache[T]{ - private var value: Option[T] = None - def apply(value: => T) = this.synchronized{ - if(!this.value.isDefined) - this.value = Some(value) - this.value.get - } -} diff --git a/stage1/CachingClassLoader.scala b/stage1/CachingClassLoader.scala index 4ddebda..38c0a1b 100644 --- a/stage1/CachingClassLoader.scala +++ b/stage1/CachingClassLoader.scala @@ -5,7 +5,7 @@ import scala.util.Try trait CachingClassLoader extends ClassLoader{ def logger: Logger - val cache = new KeyLockedLazyCache[String,Option[Class[_]]]( new ConcurrentHashMap, new ConcurrentHashMap, Some(logger) ) + val cache = new KeyLockedLazyCache[Option[Class[_]]]( new ConcurrentHashMap[AnyRef,AnyRef], Some(logger) ) override def loadClass(name: String, resolve: Boolean) = { cache.get( name, Try(super.loadClass(name, resolve)).toOption ).getOrElse(null) } 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 e430ee1..0000000 --- a/stage1/ClassLoaderCache.scala +++ /dev/null @@ -1,24 +0,0 @@ -package cbt - -import java.net._ -import java.util.concurrent.ConcurrentHashMap -import collection.JavaConversions._ - -case class ClassLoaderCache( - logger: Logger, - private[cbt] permanentKeys: ConcurrentHashMap[String,AnyRef], - private[cbt] permanentClassLoaders: ConcurrentHashMap[AnyRef,ClassLoader] -){ - val persistent = new KeyLockedLazyCache( - permanentKeys, - permanentClassLoaders, - Some(logger) - ) - override def toString = ( - s"ClassLoaderCache(" - ++ - persistent.keys.keySet.toVector.map(_.toString.split(":").mkString("\n")).sorted.mkString("\n\n","\n\n","\n\n") - ++ - ")" - ) -} diff --git a/stage1/ContextImplementation.scala b/stage1/ContextImplementation.scala index 152e606..69094b0 100644 --- a/stage1/ContextImplementation.scala +++ b/stage1/ContextImplementation.scala @@ -1,22 +1,28 @@ package cbt import java.io._ -import java.util.concurrent.ConcurrentHashMap -import java.lang._ -case class ContextImplementation( - projectDirectory: File, - cwd: File, - argsArray: Array[String], - enabledLoggersArray: Array[String], - startCompat: Long, - cbtHasChangedCompat: Boolean, - scalaVersionOrNull: String, - permanentKeys: ConcurrentHashMap[String,AnyRef], - permanentClassLoaders: ConcurrentHashMap[AnyRef,ClassLoader], - taskCache: ConcurrentHashMap[AnyRef,AnyRef], - cache: File, - cbtHome: File, - cbtRootHome: File, - compatibilityTarget: File, - parentBuildOrNull: BuildInterface -) extends Context
\ No newline at end of file +class ContextImplementation( + 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/KeyLockedLazyCache.scala b/stage1/KeyLockedLazyCache.scala index 4eff5b2..aac32a3 100644 --- a/stage1/KeyLockedLazyCache.scala +++ b/stage1/KeyLockedLazyCache.scala @@ -1,62 +1,67 @@ package cbt -import java.util.concurrent.ConcurrentHashMap - private[cbt] class LockableKey /** -A cache that lazily computes values if needed during lookup. +A hashMap that lazily computes values if needed during lookup. Locking occurs on the key, so separate keys can be looked up simultaneously without a deadlock. */ -final private[cbt] class KeyLockedLazyCache[Key <: AnyRef,Value <: AnyRef]( - val keys: ConcurrentHashMap[Key,AnyRef], - val values: ConcurrentHashMap[AnyRef,Value], +final private[cbt] class KeyLockedLazyCache[T <: AnyRef]( + val hashMap: java.util.Map[AnyRef,AnyRef], logger: Option[Logger] ){ - def get( key: Key, value: => Value ): Value = { - val lockableKey = keys.synchronized{ - if( ! (keys containsKey key) ){ + final val seen = new ThreadLocal[collection.mutable.Set[AnyRef]](){ + override protected def initialValue = collection.mutable.Set[AnyRef](); + } + def get( key: AnyRef, value: => T ): T = { + seen.get.add( key ); + val lockableKey = hashMap.synchronized{ + if( ! (hashMap containsKey key) ){ val lockableKey = new LockableKey //logger.foreach(_.resolver("CACHE MISS: " ++ key.toString)) - keys.put( key, lockableKey ) + hashMap.put( key, lockableKey ) lockableKey } else { - val lockableKey = keys get key + val lockableKey = hashMap get key //logger.foreach(_.resolver("CACHE HIT: " ++ lockableKey.toString ++ " -> " ++ key.toString)) lockableKey } } import collection.JavaConversions._ - //logger.resolver("CACHE: \n" ++ keys.mkString("\n")) + //logger.resolver("CACHE: \n" ++ hashMap.mkString("\n")) // synchronizing on key only, so asking for a particular key does - // not block the whole cache, but just that cache entry + // not block the whole hashMap, but just that hashMap entry lockableKey.synchronized{ - if( ! (values containsKey lockableKey) ){ - values.put( lockableKey, value ) + if( ! (hashMap containsKey lockableKey) ){ + hashMap.put( lockableKey, value ) } - values get lockableKey + (hashMap get lockableKey).asInstanceOf[T] } } - def update( key: Key, value: Value ): Value = { - val lockableKey = keys get key + def update( key: AnyRef, value: T ): T = { + assert( + !seen.get.contains( key ), + "Thread tries to update cache key after observing it: " + key + ) + val lockableKey = hashMap get key lockableKey.synchronized{ - values.put( lockableKey, value ) + hashMap.put( lockableKey, value ) value } } - def remove( key: Key ) = keys.synchronized{ - assert(keys containsKey key) - val lockableKey = keys get key + def remove( key: AnyRef ) = hashMap.synchronized{ + assert(hashMap containsKey key) + val lockableKey = hashMap get key lockableKey.synchronized{ - if(values containsKey lockableKey){ - // this is so values in the process of being replaced (which mean they have a key but no value) + if(hashMap containsKey lockableKey){ + // this is so hashMap in the process of being replaced (which mean they have a key but no value) // are not being removed - keys.remove( key ) - values.remove( lockableKey ) + hashMap.remove( key ) + hashMap.remove( lockableKey ) } } } - def containsKey( key: Key ) = keys.synchronized{ - keys containsKey key + def containsKey( key: AnyRef ) = hashMap.synchronized{ + hashMap containsKey key } } diff --git a/stage1/MavenRepository.scala b/stage1/MavenRepository.scala index 4184d2d..a8c9b51 100644 --- a/stage1/MavenRepository.scala +++ b/stage1/MavenRepository.scala @@ -1,9 +1,13 @@ package cbt import java.io._ import java.net._ -case class MavenResolver( cbtHasChanged: Boolean, mavenCache: File, urls: URL* ){ - def bind( dependencies: MavenDependency* )(implicit logger: Logger): Seq[BoundMavenDependency] - = dependencies.map( BoundMavenDependency(cbtHasChanged,mavenCache,_,urls.to) ).to - def bindOne( dependency: MavenDependency )(implicit logger: Logger): BoundMavenDependency - = BoundMavenDependency( cbtHasChanged, mavenCache, dependency, urls.to ) +case class MavenResolver( + cbtLastModified: Long, mavenCache: File, urls: URL* +)( + implicit logger: Logger, transientCache: java.util.Map[AnyRef,AnyRef] +){ + def bind( dependencies: MavenDependency* ): Seq[BoundMavenDependency] + = dependencies.map( BoundMavenDependency(cbtLastModified,mavenCache,_,urls.to) ).to + def bindOne( dependency: MavenDependency ): BoundMavenDependency + = BoundMavenDependency( cbtLastModified, mavenCache, dependency, urls.to ) } diff --git a/stage1/PoorMansProfiler.scala b/stage1/PoorMansProfiler.scala index b7aa47d..4bc44ba 100644 --- a/stage1/PoorMansProfiler.scala +++ b/stage1/PoorMansProfiler.scala @@ -1,10 +1,10 @@ /* // temporary debugging tool package cbt -import java.util.concurrent.ConcurrentHashMap +import java.util._ import collection.JavaConversions._ object PoorMansProfiler{ - val entries = new ConcurrentHashMap[String, Long] + val entries = new HashMap[String, Long] def profile[T](name: String)(code: => T): T = { val before = System.currentTimeMillis if(!(entries containsKey name)){ diff --git a/stage1/Stage1.scala b/stage1/Stage1.scala index d559dd4..bf6a3d4 100644 --- a/stage1/Stage1.scala +++ b/stage1/Stage1.scala @@ -1,7 +1,7 @@ package cbt import java.io._ -import java.util.concurrent.ConcurrentHashMap +import java.util._ import scala.collection.JavaConverters._ @@ -33,24 +33,24 @@ final case class Stage1ArgsParser(__args: Seq[String]) { abstract class Stage2Base{ - def run( context: Stage2Args ): Unit + def run( context: Stage2Args ): ExitCode } case class Stage2Args( cwd: File, args: Seq[String], - cbtHasChanged: Boolean, + stage2LastModified: Long, + logger: Logger, classLoaderCache: ClassLoaderCache, cache: File, cbtHome: File, compatibilityTarget: File +)( + implicit val transientCache: java.util.Map[AnyRef,AnyRef] ){ - val ClassLoaderCache( - logger, - permanentKeys, - permanentClassLoaders - ) = classLoaderCache + val persistentCache = classLoaderCache.hashMap } + object Stage1{ protected def newerThan( a: File, b: File ) ={ a.lastModified > b.lastModified @@ -58,17 +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.permanentKeys, - context.permanentClassLoaders - ), + new ClassLoaderCache( context.persistentCache ), context.cbtHome, - context.cache - ) + context.cache, + logger + )(context.transientCache) classLoader .loadClass("cbt.Stage2") @@ -76,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 - ): (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._ @@ -94,62 +92,59 @@ object Stage1{ val stage2sourceFiles = ( 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 = 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.dependencyClasspath, + 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.persistent.containsKey( cbtDependency.classpath.string ) ) { - classLoaderCache.persistent.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.persistent.containsKey( cbtDependency.stage1Dependency.compatibilityDependency.classpath.string ), - "cbt unchanged, expected compatibility classloader to be cached" - ) - assert( - classLoaderCache.persistent.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( @@ -158,7 +153,7 @@ object Stage1{ ) } - ( cbtHasChanged, stage2ClassLoader ) + ( stage2LastModified, stage2ClassLoader ) } def run( @@ -166,28 +161,23 @@ object Stage1{ cache: File, cbtHome: File, buildStage1: BuildStage1Result, - start: java.lang.Long, - classLoaderCacheKeys: ConcurrentHashMap[String,AnyRef], - classLoaderCacheValues: ConcurrentHashMap[AnyRef,ClassLoader] + 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, - classLoaderCacheKeys, - classLoaderCacheValues - ) - + 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 273b9af..f73995e 100644 --- a/stage1/Stage1Lib.scala +++ b/stage1/Stage1Lib.scala @@ -9,7 +9,6 @@ import java.nio.file.attribute.FileTime import javax.tools._ import java.security._ import java.util.{Set=>_,Map=>_,List=>_,_} -import java.util.concurrent.ConcurrentHashMap import javax.xml.bind.annotation.adapters.HexBinaryAdapter // CLI interop @@ -33,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 @@ -101,7 +100,7 @@ class Stage1Lib( val logger: Logger ) extends BaseLib{ trapExitCode{ val c = classLoader.loadClass(cls) val m = c.getMethod( "main", classOf[Array[String]] ) - val instance = + val instance = if(!fakeInstance) null else c.newInstance assert( fakeInstance || (m.getModifiers & java.lang.reflect.Modifier.STATIC) > 0, @@ -184,37 +183,37 @@ 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, - classpath: ClassPath, + dependencies: Seq[Dependency], mavenCache: File, scalacOptions: Seq[String] = Seq(), classLoaderCache: ClassLoaderCache, zincVersion: String, scalaVersion: String - ): Option[File] = { - + )( + implicit transientCache: java.util.Map[AnyRef, AnyRef] + ): 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 @ @@ -241,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( @@ -255,7 +252,7 @@ class Stage1Lib( val logger: Logger ) extends BaseLib{ ) val singleArgs = scalacOptions.map( "-S" ++ _ ) - val code = + val code = redirectOutToErr{ System.err.println("Compiling to " ++ compileTarget.toString) try{ @@ -263,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 { @@ -282,9 +279,11 @@ class Stage1Lib( val logger: Logger ) extends BaseLib{ -cp \\ ${classpath.strings.mkString(":\\\n")} \\ \\ - ${files.sorted.mkString(" \\\n")} + ${sourceFiles.sorted.mkString(" \\\n")} """ ) + + redirectOutToErr( e.printStackTrace ) ExitCode.Failure } } @@ -297,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 = { @@ -357,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 ) @@ -377,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 @@ -416,32 +417,61 @@ class Stage1Lib( val logger: Logger ) extends BaseLib{ } def classLoaderRecursion( dependency: Dependency, latest: Map[(String,String),Dependency], cache: ClassLoaderCache ): ClassLoader = { - val d = dependency + // FIXME: shouldn't we be using KeyLockedLazyCache instead of hashmap directly here? 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() ) + NailgunLauncher.jdkClassLoader } else if( dependencies.size == 1 ){ classLoaderRecursion( dependencies.head, latest, cache ) } else{ - val cp = d.dependencyClasspath.string - if( dependencies.exists(_.needsUpdate) && cache.persistent.containsKey(cp) ){ - cache.persistent.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.persistent.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.persistent.get( a.classpath.string, cl ) + 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 ) + } + +} + +import scala.reflect._ +import scala.language.existentials +case class PerClassCache(cache: java.util.Map[AnyRef,AnyRef], moduleKey: String)(implicit logger: Logger){ + def apply[D <: Dependency: ClassTag](key: AnyRef): MethodCache[D] = new MethodCache[D](key) + case class MethodCache[D <: Dependency: ClassTag](key: AnyRef){ + def memoize[T <: AnyRef](task: => T): T = { + val fullKey = (classTag[D].runtimeClass, moduleKey, key) + logger.transientCache("fetching key"+fullKey) + if( cache.containsKey(fullKey) ){ + logger.transientCache("found key"+fullKey) + cache.get(fullKey).asInstanceOf[T] + } else{ + val value = task + logger.transientCache("put key"+fullKey) + cache.put( fullKey, value ) + value + } + } } } diff --git a/stage1/cbt.scala b/stage1/cbt.scala index e324fa0..0b0ccbf 100644 --- a/stage1/cbt.scala +++ b/stage1/cbt.scala @@ -2,7 +2,6 @@ package cbt import java.io._ import java.nio.file._ import java.net._ -import java.util.concurrent.ConcurrentHashMap object `package`{ implicit class TypeInferenceSafeEquals[T](value: T){ @@ -34,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 @@ -53,47 +57,43 @@ 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, - permanentKeys, - permanentClassLoaders - ) - def cbtDependency = { + + def classLoaderCache: ClassLoaderCache = new ClassLoaderCache( persistentCache ) + def cbtDependencies = { import paths._ - CbtDependency(cbtHasChanged, mavenCache, nailgunTarget, stage1Target, stage2Target, compatibilityTarget) + 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 - ): Context = ContextImplementation( + ): Context = new ContextImplementation( projectDirectory, cwd, args.to, enabledLoggers.to, - startCompat, - cbtHasChangedCompat, + start, + cbtLastModified, scalaVersion.getOrElse(null), - permanentKeys, - permanentClassLoaders, - taskCache, + persistentCache, + transientCache, cache, cbtHome, cbtRootHome, diff --git a/stage1/logger.scala b/stage1/logger.scala index effdc35..8c8431a 100644 --- a/stage1/logger.scala +++ b/stage1/logger.scala @@ -41,6 +41,7 @@ case class Logger(enabledLoggers: Set[String], start: Long) { final def git(msg: => String) = log(names.git, msg) final def pom(msg: => String) = log(names.pom, msg) final def dynamic(msg: => String) = log(names.dynamic, msg) + final def transientCache(msg: => String) = log(names.transientCache, msg) private object names{ val stage1 = "stage1" @@ -54,6 +55,7 @@ case class Logger(enabledLoggers: Set[String], start: Long) { val pom = "pom" val git = "git" val dynamic = "dynamic" + val transientCache = "transientCache" } private def logUnguarded(name: String, msg: => String) = { diff --git a/stage1/resolver.scala b/stage1/resolver.scala index ff5ad68..e6e4588 100644 --- a/stage1/resolver.scala +++ b/stage1/resolver.scala @@ -4,29 +4,28 @@ import java.nio.charset.StandardCharsets import java.net._ import java.io._ import scala.xml._ -import scala.concurrent._ -import scala.concurrent.duration._ trait DependencyImplementation extends Dependency{ implicit protected def logger: Logger protected def lib = new Stage1Lib(logger) + implicit protected def transientCache: java.util.Map[AnyRef,AnyRef] + /** key used by taskCache to identify different objects that represent the same logical module */ + protected def moduleKey: String /** - 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 + caches given value in context keyed with given key and projectDirectory + the context is fresh on every complete run of cbt + */ + protected lazy val taskCache = new PerClassCache(transientCache, moduleKey) + 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]] @@ -103,11 +102,11 @@ trait DependencyImplementation extends Dependency{ ) def dependencies: Seq[Dependency] - private object transitiveDependenciesCache extends Cache[Seq[Dependency]] /** return dependencies in order of linearized dependence. this is a bit tricky. */ - def transitiveDependencies: Seq[Dependency] = transitiveDependenciesCache{ - lib.transitiveDependencies(this) - } + def transitiveDependencies: Seq[Dependency] = + taskCache[DependencyImplementation]( "transitiveDependencies" ).memoize{ + lib.transitiveDependencies(this) + } override def show: String = this.getClass.getSimpleName // ========== debug ========== @@ -115,64 +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) 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) 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) 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)) -case class ScalaDependencies(cbtHasChanged: Boolean, mavenCache: File, version: String)(implicit val logger: Logger) extends DependencyImplementation{ sd => - override final val needsUpdate = false - def targetClasspath = ClassPath() - def exportedClasspath = ClassPath() - def dependencies = Seq( - new ScalaCompilerDependency(cbtHasChanged, mavenCache, version), - new ScalaLibraryDependency(cbtHasChanged, mavenCache, version), - new ScalaReflectDependency(cbtHasChanged, mavenCache, version) +class ScalaDependencies(cbtLastModified: Long, mavenCache: File, version: String)(implicit logger: Logger, transientCache: java.util.Map[AnyRef,AnyRef]) extends Dependencies( + Seq( + new ScalaCompilerDependency(cbtLastModified, mavenCache, version), + new ScalaLibraryDependency(cbtLastModified, mavenCache, version), + new ScalaReflectDependency(cbtLastModified, mavenCache, version) ) -} +) -case class BinaryDependency( path: File, dependencies: Seq[Dependency] )(implicit val logger: Logger) extends DependencyImplementation{ - def exportedClasspath = ClassPath(Seq(path)) - override def needsUpdate = false +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 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) extends DependencyImplementation{ - override def needsUpdate = dependencies.exists(_.needsUpdate) - override def exportedClasspath = ClassPath() - override def targetClasspath = ClassPath() +case class Dependencies( dependencies: Seq[Dependency] )(implicit val logger: Logger, val transientCache: java.util.Map[AnyRef,AnyRef]) extends DependencyImplementation{ + override def lastModified = dependencies.map(_.lastModified).maxOption.getOrElse(0) + def moduleKey = this.getClass.getName ++ "(" ++ dependencies.map(_.moduleKey).mkString(", ") ++ ")" + def targetClasspath = ClassPath() + def exportedClasspath = ClassPath() + override def show: String = this.getClass.getSimpleName + "( " + dependencies.map(_.show).mkString(", ") + " )" } -case class Stage1Dependency(cbtHasChanged: Boolean, mavenCache: File, nailgunTarget: File, stage1Target: File, compatibilityTarget: File)(implicit val logger: Logger) extends DependencyImplementation{ - override def needsUpdate = false +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(nailgunTarget, stage1Target) ) - val compatibilityDependency = CompatibilityDependency(cbtHasChanged, compatibilityTarget) - override def dependencies = Seq( - compatibilityDependency - ) ++ - MavenResolver(cbtHasChanged,mavenCache,mavenCentral).bind( - MavenDependency("org.scala-lang","scala-library",constants.scalaVersion), - MavenDependency("org.scala-lang.modules","scala-xml_"+constants.scalaMajorVersion,constants.scalaXmlVersion) - ) + override def exportedClasspath = ClassPath( Seq(target) ) + override def dependencies = _dependencies } -case class CompatibilityDependency(cbtHasChanged: Boolean, compatibilityTarget: File)(implicit val logger: Logger) extends DependencyImplementation{ - override def needsUpdate = false - override def targetClasspath = exportedClasspath - override def exportedClasspath = ClassPath( Seq(compatibilityTarget) ) - override def dependencies = Seq() -} -case class CbtDependency(cbtHasChanged: Boolean, mavenCache: File, nailgunTarget: File, stage1Target: File, stage2Target: File, compatibilityTarget: File)(implicit val logger: Logger) extends DependencyImplementation{ - override def needsUpdate = false - override def targetClasspath = exportedClasspath - override def exportedClasspath = ClassPath( Seq( stage2Target ) ) - val stage1Dependency = Stage1Dependency(cbtHasChanged, mavenCache, nailgunTarget, stage1Target, compatibilityTarget) - override def dependencies = Seq( - stage1Dependency - ) ++ - 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") + ) ) } @@ -186,9 +184,11 @@ abstract class DependenciesProxy{ } class BoundMavenDependencies( - cbtHasChanged: Boolean, mavenCache: File, urls: Seq[URL], mavenDependencies: Seq[MavenDependency] -)(implicit logger: Logger) extends Dependencies( - mavenDependencies.map( BoundMavenDependency(cbtHasChanged,mavenCache,_,urls) ) + cbtLastModified: Long, mavenCache: File, urls: Seq[URL], mavenDependencies: Seq[MavenDependency] +)( + implicit logger: Logger, transientCache: java.util.Map[AnyRef,AnyRef] +) extends Dependencies( + mavenDependencies.map( BoundMavenDependency(cbtLastModified,mavenCache,_,urls) ) ) case class MavenDependency( groupId: String, artifactId: String, version: String, classifier: Classifier = Classifier.none @@ -202,8 +202,11 @@ object MavenDependency{ } // FIXME: take MavenResolver instead of mavenCache and repositories separately case class BoundMavenDependency( - cbtHasChanged: Boolean, mavenCache: File, mavenDependency: MavenDependency, repositories: Seq[URL] -)(implicit val logger: Logger) extends ArtifactInfo with DependencyImplementation{ + cbtLastModified: Long, mavenCache: File, mavenDependency: MavenDependency, repositories: Seq[URL] +)( + implicit val logger: Logger, val transientCache: java.util.Map[AnyRef,AnyRef] +) extends ArtifactInfo with DependencyImplementation{ + def moduleKey = this.getClass.getName ++ "(" ++ mavenDependency.serialize ++ ")" val MavenDependency( groupId, artifactId, version, classifier ) = mavenDependency assert( Option(groupId).collect{ @@ -221,13 +224,13 @@ case class BoundMavenDependency( version != "" && version != null && !version.startsWith(" ") && !version.endsWith(" "), s"not a valid version: '$version'" ) - override def show: String = this.getClass.getSimpleName ++ "(" ++ mavenDependency.serialize ++ ")" + 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 "") - + //private def coursierJarFile = userHome++"/.coursier/cache/v1/https/repo1.maven.org/maven2"++basePath++".jar" def exportedJars = Seq( jar ) @@ -253,17 +256,11 @@ case class BoundMavenDependency( StandardCharsets.UTF_8 ).mkString("\n").split(" ").head.trim } - - private object jarSha1Cache extends Cache[String] - def jarSha1: String = jarSha1Cache{ resolveHash("jar", true) } - - private object pomSha1Cache extends Cache[String] - def pomSha1: String = pomSha1Cache{ resolveHash("pom", false) } - private object jarCache extends Cache[File] - def jar: File = jarCache{ resolve("jar", Some(jarSha1), true) } - private object pomCache extends Cache[File] - def pom: File = pomCache{ resolve("pom", Some(pomSha1), false) } + def jarSha1: String = taskCache[BoundMavenDependency]("jarSha1").memoize{ resolveHash("jar", true) } + def pomSha1: String = taskCache[BoundMavenDependency]("pomSha1").memoize{ resolveHash("pom", false) } + def jar: File = taskCache[BoundMavenDependency]("jar").memoize{ resolve("jar", Some(jarSha1), true) } + def pom: File = taskCache[BoundMavenDependency]("pom").memoize{ resolve("pom", Some(pomSha1), false) } private def pomXml = XML.loadFile(pom.string) // ========== pom traversal ========== @@ -272,7 +269,7 @@ case class BoundMavenDependency( (pomXml \ "parent").collect{ case parent => BoundMavenDependency( - cbtHasChanged: Boolean, + cbtLastModified: Long, mavenCache, MavenDependency( (parent \ "groupId").text, @@ -280,7 +277,7 @@ case class BoundMavenDependency( (parent \ "version").text ), repositories - )(logger) + ) }.flatMap(_.transitivePom) :+ this } @@ -310,7 +307,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" => @@ -335,7 +332,7 @@ case class BoundMavenDependency( MavenDependency( groupId, artifactId, version, classifier ) }.toVector }.map( - BoundMavenDependency( cbtHasChanged, mavenCache, _, repositories ) + BoundMavenDependency( cbtLastModified, mavenCache, _, repositories ) ).to } } |