diff options
author | Jan Christopher Vogt <oss.nsp@cvogt.org> | 2016-04-07 00:37:20 -0400 |
---|---|---|
committer | Jan Christopher Vogt <oss.nsp@cvogt.org> | 2016-04-07 00:37:20 -0400 |
commit | 0ef27a7d8cabd0dfe4009b09481566d3d02a76c6 (patch) | |
tree | 43f0cbea4fa66414f59763b589d029fd43cb7e6b | |
parent | 2d1a51d64aaca9ad00057a5bb822a50158b67429 (diff) | |
parent | ce8bab3856ec8755fb3b99be324f090770ddfe1f (diff) | |
download | cbt-0ef27a7d8cabd0dfe4009b09481566d3d02a76c6.tar.gz cbt-0ef27a7d8cabd0dfe4009b09481566d3d02a76c6.tar.bz2 cbt-0ef27a7d8cabd0dfe4009b09481566d3d02a76c6.zip |
Merge pull request #102 from cvogt/chris
More classloading related fixes
-rw-r--r-- | TODO.txt | 1 | ||||
-rw-r--r-- | stage1/ClassLoaderCache.scala | 12 | ||||
-rw-r--r-- | stage1/Stage1.scala | 14 | ||||
-rw-r--r-- | stage1/Stage1Lib.scala | 2 | ||||
-rw-r--r-- | stage1/logger.scala | 2 | ||||
-rw-r--r-- | stage1/resolver.scala | 63 | ||||
-rw-r--r-- | stage2/AdminStage2.scala | 2 | ||||
-rw-r--r-- | stage2/AdminTasks.scala | 6 | ||||
-rw-r--r-- | stage2/BasicBuild.scala | 6 | ||||
-rw-r--r-- | stage2/BuildBuild.scala | 24 | ||||
-rw-r--r-- | stage2/BuildDependency.scala | 4 | ||||
-rw-r--r-- | stage2/GitDependency.scala | 2 | ||||
-rw-r--r-- | stage2/Lib.scala | 18 | ||||
-rw-r--r-- | stage2/Stage2.scala | 7 | ||||
-rw-r--r-- | test/test.scala | 2 |
15 files changed, 84 insertions, 81 deletions
@@ -3,7 +3,6 @@ TODO: - improve logging - immediate features - - maybe rename context.cwd - fix main project main method being run during tests - investigate and solve multiple compilations of the same SourceDependency Build. Maybe introduce global Build map. diff --git a/stage1/ClassLoaderCache.scala b/stage1/ClassLoaderCache.scala index 10d872d..44b8d7d 100644 --- a/stage1/ClassLoaderCache.scala +++ b/stage1/ClassLoaderCache.scala @@ -15,5 +15,15 @@ class ClassLoaderCache(logger: Logger){ new ConcurrentHashMap[AnyRef,ClassLoader], Some(logger) ) - override def toString = s"""ClassLoaderCache("""+ persistent.keys.keySet.toVector.map(_.toString).sorted.map(" "++_).mkString("\n","\n","\n") +""")""" + override def toString = ( + s"ClassLoaderCache(" + ++ + persistent.keys.keySet.toVector.map(_.toString.split(":").mkString("\n")).sorted.mkString("\n\n","\n\n","\n\n") + ++ + "---------" + ++ + transient.keys.keySet.toVector.map(_.toString.split(":").mkString("\n")).sorted.mkString("\n\n","\n\n^","\n\n") + ++ + ")" + ) } diff --git a/stage1/Stage1.scala b/stage1/Stage1.scala index 3456e1f..77b88a2 100644 --- a/stage1/Stage1.scala +++ b/stage1/Stage1.scala @@ -41,7 +41,8 @@ case class Stage2Args( cwd: File, args: Seq[String], cbtHasChanged: Boolean, - logger: Logger + logger: Logger, + classLoaderCache: ClassLoaderCache ) object Stage1{ @@ -70,13 +71,17 @@ object Stage1{ ) logger.stage1(s"calling CbtDependency.classLoader") - if(cbtHasChanged){ + if(cbtHasChanged) { NailgunLauncher.stage2classLoader = CbtDependency().classLoader(classLoaderCache) + }else{ + classLoaderCache.transient.get( CbtDependency().classpath.string, NailgunLauncher.stage2classLoader ) } logger.stage1(s"Run Stage2") + val cl = NailgunLauncher.stage2classLoader val exitCode = ( - NailgunLauncher.stage2classLoader.loadClass( + cl + .loadClass( if(args.admin) "cbt.AdminStage2" else "cbt.Stage2" ) .getMethod( "run", classOf[Stage2Args] ) @@ -87,7 +92,8 @@ object Stage1{ args.args.drop(1).toVector, // launcher changes cause entire nailgun restart, so no need for them here cbtHasChanged = cbtHasChanged, - logger + logger, + classLoaderCache ) ) match { case code: ExitCode => code diff --git a/stage1/Stage1Lib.scala b/stage1/Stage1Lib.scala index da9f8dd..7a2f8db 100644 --- a/stage1/Stage1Lib.scala +++ b/stage1/Stage1Lib.scala @@ -31,7 +31,7 @@ object CatchTrappedExitCode{ } } -case class Context( cwd: File, args: Seq[String], logger: Logger, cbtHasChanged: Boolean, classLoaderCache: ClassLoaderCache ) +case class Context( projectDirectory: File, cwd: File, args: Seq[String], logger: Logger, cbtHasChanged: Boolean, classLoaderCache: ClassLoaderCache ) class BaseLib{ def realpath(name: File) = new File(Paths.get(name.getAbsolutePath).normalize.toString) diff --git a/stage1/logger.scala b/stage1/logger.scala index 91a2412..c21dc86 100644 --- a/stage1/logger.scala +++ b/stage1/logger.scala @@ -11,7 +11,7 @@ case class Logger(enabledLoggers: Set[String], start: Long) { def this(enabledLoggers: Option[String], start: Long) = this( enabledLoggers.toVector.flatMap( _.split(",") ).toSet, start ) def log(name: String, msg: => String) = { - val timeTaken = ((start - System.currentTimeMillis) / 1000).toString + val timeTaken = ((System.currentTimeMillis.toDouble - start) / 1000).toString System.err.println( s"[${" "*(6-timeTaken.size)}$timeTaken][$name] $msg" ) } diff --git a/stage1/resolver.scala b/stage1/resolver.scala index e8bfc07..e4af8a9 100644 --- a/stage1/resolver.scala +++ b/stage1/resolver.scala @@ -60,10 +60,9 @@ abstract class Dependency{ The implementation of this method is untested and likely buggy at this stage. */ - private object cacheExportClasspathConcurrently extends Cache[Future[ClassPath]] private def exportClasspathConcurrently( latest: Map[(String, String),ArtifactInfo]//, cache: BuildCache - )( implicit ec: ExecutionContext ): Future[ClassPath] = cacheExportClasspathConcurrently{ + )( implicit ec: ExecutionContext ): Future[ClassPath] = { Future.sequence( // trigger compilation / download of all dependencies first this.dependencies.map{ d => @@ -86,7 +85,7 @@ abstract class Dependency{ ) } - private def actual(current: Dependency, latest: Map[(String,String),Dependency]) = current match { + protected def actual(current: Dependency, latest: Map[(String,String),Dependency]) = current match { case d: ArtifactInfo => latest((d.groupId,d.artifactId)) case d => d } @@ -104,34 +103,28 @@ abstract class Dependency{ ) ) } else { - val (cachable, nonCachable) = dependencies.partition(_.canBeCached) - new URLClassLoader( - ClassPath.flatten( nonCachable.map(actual(_,latest)).map(_.exportedClasspath) ), - cache.persistent.get( - ClassPath.flatten( cachable.map(actual(_,latest)).map(_.exportedClasspath) ).string, - new MultiClassLoader( - cachable.map( _.classLoaderRecursion(latest, cache) ) - ) + cache.transient.get( + dependencyClasspath.string, + new MultiClassLoader( + dependencies.map( _.classLoaderRecursion(latest, cache) ) ) ) - new MultiClassLoader( - dependencies.map( _.classLoaderRecursion(latest, cache) ) - ) } } protected def classLoaderRecursion( latest: Map[(String,String),Dependency], cache: ClassLoaderCache ): ClassLoader = { - if( canBeCached ){ - val a = actual( this, latest ) - cache.persistent.get( - a.classpath.string, - new cbt.URLClassLoader( a.exportedClasspath, dependencyClassLoader(latest, cache) ) - ) - } else { - new cbt.URLClassLoader( exportedClasspath, dependencyClassLoader(latest, cache) ) - } + val a = actual( this, latest ) + ( + if( canBeCached ){ + cache.persistent + } else { + cache.transient + } + ).get( + a.classpath.string, + new cbt.URLClassLoader( a.exportedClasspath, dependencyClassLoader(latest, cache) ) + ) } - private object classLoaderCache extends Cache[ClassLoader] - def classLoader( cache: ClassLoaderCache ): ClassLoader = classLoaderCache{ + def classLoader( cache: ClassLoaderCache ): ClassLoader = { if( concurrencyEnabled ){ // trigger concurrent building / downloading dependencies exportClasspathConcurrently @@ -145,6 +138,7 @@ abstract class Dependency{ cache ) } + // FIXME: these probably need to update outdated as well def classpath : ClassPath = exportedClasspath ++ dependencyClasspath def dependencyJars : Seq[File] = transitiveDependencies.flatMap(_.jars) def dependencyClasspath : ClassPath = ClassPath.flatten( transitiveDependencies.map(_.exportedClasspath) ) @@ -222,16 +216,6 @@ object Dependencies{ case class Stage1Dependency()(implicit val logger: Logger) extends Dependency{ override def needsUpdate = false // FIXME: think this through, might allow simplifications and/or optimizations override def canBeCached = false - /* - private object classLoaderRecursionCache extends Cache[ClassLoader] - override def classLoaderRecursion(latest: Map[(String,String),Dependency], cache: ClassLoaderCache) = classLoaderRecursionCache{ - println(System.currentTimeMillis) - val cl = getClass.getClassLoader - println(System.currentTimeMillis) - cl - ClassLoader.getSystemClassLoader - } - */ override def targetClasspath = exportedClasspath override def exportedClasspath = ClassPath( Seq(nailgunTarget, stage1Target) ) override def exportedJars = ???//Seq[File]() @@ -242,8 +226,13 @@ case class Stage1Dependency()(implicit val logger: Logger) extends Dependency{ ) ) // FIXME: implement sanity check to prevent using incompatible scala-library and xml version on cp - override def classLoaderRecursion( latest: Map[(String,String),Dependency], cache: ClassLoaderCache ) - = getClass.getClassLoader + override def classLoaderRecursion( latest: Map[(String,String),Dependency], cache: ClassLoaderCache ) = { + val a = actual( this, latest ) + cache.transient.get( + a.classpath.string, + getClass.getClassLoader + ) + } } case class CbtDependency()(implicit val logger: Logger) extends Dependency{ override def needsUpdate = false // FIXME: think this through, might allow simplifications and/or optimizations diff --git a/stage2/AdminStage2.scala b/stage2/AdminStage2.scala index 883b5ed..9d7dcb2 100644 --- a/stage2/AdminStage2.scala +++ b/stage2/AdminStage2.scala @@ -4,7 +4,7 @@ object AdminStage2 extends Stage2Base{ def run( _args: Stage2Args ): Unit = { val args = _args.args.dropWhile(Seq("admin","direct") contains _) val lib = new Lib(_args.logger) - val adminTasks = new AdminTasks(lib, args, _args.cwd) + val adminTasks = new AdminTasks(lib, args, _args.cwd, _args.classLoaderCache) new lib.ReflectObject(adminTasks){ def usage: String = "Available methods: " ++ lib.taskNames(adminTasks.getClass).mkString(" ") }.callNullary(args.lift(0)) diff --git a/stage2/AdminTasks.scala b/stage2/AdminTasks.scala index 655b2b0..f189805 100644 --- a/stage2/AdminTasks.scala +++ b/stage2/AdminTasks.scala @@ -2,7 +2,7 @@ package cbt import scala.collection.immutable.Seq import java.io.{Console=>_,_} import java.nio.file._ -class AdminTasks(lib: Lib, args: Seq[String], cwd: File){ +class AdminTasks(lib: Lib, args: Seq[String], cwd: File, classLoaderCache: ClassLoaderCache){ implicit val logger: Logger = lib.logger def resolve = { ClassPath.flatten( @@ -31,14 +31,14 @@ class AdminTasks(lib: Lib, args: Seq[String], cwd: File){ ) // FIXME: this does not work quite yet, throws NoSuchFileException: /ammonite/repl/frontend/ReplBridge$.class lib.runMain( - "ammonite.repl.Main", Seq(), d.classLoader(new ClassLoaderCache(logger)) + "ammonite.repl.Main", Seq(), d.classLoader(classLoaderCache) ) } def scala = { val version = args.lift(1).getOrElse(constants.scalaVersion) val scalac = new ScalaCompilerDependency( version ) lib.runMain( - "scala.tools.nsc.MainGenericRunner", Seq("-cp", scalac.classpath.string), scalac.classLoader(new ClassLoaderCache(logger)) + "scala.tools.nsc.MainGenericRunner", Seq("-cp", scalac.classpath.string), scalac.classLoader(classLoaderCache) ) } def scaffoldBasicBuild: Unit = lib.scaffoldBasicBuild( cwd ) diff --git a/stage2/BasicBuild.scala b/stage2/BasicBuild.scala index 39cc9e3..2b49c93 100644 --- a/stage2/BasicBuild.scala +++ b/stage2/BasicBuild.scala @@ -23,7 +23,7 @@ class Build(val context: Context) extends Dependency with TriggerLoop with SbtDe override def canBeCached = false def enableConcurrency = false - final def projectDirectory: File = lib.realpath(context.cwd) + final def projectDirectory: File = lib.realpath(context.projectDirectory) assert( projectDirectory.exists, "projectDirectory does not exist: " ++ projectDirectory.string ) final def usage: String = lib.usage(this.getClass, context) @@ -89,7 +89,7 @@ class Build(val context: Context) extends Dependency with TriggerLoop with SbtDe ) = lib.ScalaDependency( groupId, artifactId, version, classifier, scalaVersion ) final def BuildDependency(path: File) = cbt.BuildDependency( - context.copy( cwd = path, args = Seq() ) + context.copy( projectDirectory = path, args = Seq() ) ) def triggerLoopFiles: Seq[File] = sources ++ transitiveDependencies.collect{ case b: TriggerLoop => b.triggerLoopFiles }.flatten @@ -133,6 +133,7 @@ class Build(val context: Context) extends Dependency with TriggerLoop with SbtDe def test: ExitCode = lib.test(context) + /* context.logger.composition(">"*80) context.logger.composition("class " ++ this.getClass.toString) context.logger.composition("dir " ++ projectDirectory.string) @@ -141,6 +142,7 @@ class Build(val context: Context) extends Dependency with TriggerLoop with SbtDe context.logger.composition("context " ++ context.toString) context.logger.composition("dependencyTree\n" ++ dependencyTree) context.logger.composition("<"*80) + */ // ========== cbt internals ========== private[cbt] def finalBuild = this diff --git a/stage2/BuildBuild.scala b/stage2/BuildBuild.scala index 813b44b..c52c3c6 100644 --- a/stage2/BuildBuild.scala +++ b/stage2/BuildBuild.scala @@ -6,22 +6,20 @@ class BuildBuild(context: Context) extends Build(context){ override def dependencies = Seq( CbtDependency()(context.logger) ) ++ super.dependencies def managedBuildDirectory: File = lib.realpath( projectDirectory.parent ) val managedBuild = try{ - val managedContext = context.copy( cwd = managedBuildDirectory ) - val cl = new cbt.URLClassLoader( - exportedClasspath, - classOf[BuildBuild].getClassLoader // FIXME: this looks wrong. Should be ClassLoader.getSystemClassLoader but that crashes - ) - cl - .loadClass(lib.buildClassName) - .getConstructor(classOf[Context]) - .newInstance(managedContext) - .asInstanceOf[Build] + val managedContext = context.copy( projectDirectory = managedBuildDirectory ) + val cl = classLoader(context.classLoaderCache) + logger.composition("Loading build at "+managedContext.projectDirectory) + cl + .loadClass(lib.buildClassName) + .getConstructor(classOf[Context]) + .newInstance(managedContext) + .asInstanceOf[Build] } catch { case e: ClassNotFoundException if e.getMessage == lib.buildClassName => - throw new Exception("You need to remove the directory or define a class Build in: "+context.cwd) + throw new Exception("You need to remove the directory or define a class Build in: "+context.projectDirectory) case e: Exception => - throw new Exception("during build: "+context.cwd, e) + throw new Exception("during build: "+context.projectDirectory, e) } override def triggerLoopFiles = super.triggerLoopFiles ++ managedBuild.triggerLoopFiles - override def finalBuild = managedBuild.finalBuild + override def finalBuild = if( context.projectDirectory == context.cwd ) this else managedBuild.finalBuild } diff --git a/stage2/BuildDependency.scala b/stage2/BuildDependency.scala index 19357f9..8965cee 100644 --- a/stage2/BuildDependency.scala +++ b/stage2/BuildDependency.scala @@ -16,7 +16,7 @@ trait TriggerLoop extends Dependency{ } /** You likely want to use the factory method in the BasicBuild class instead of this. */ case class BuildDependency(context: Context) extends TriggerLoop{ - override def show = this.getClass.getSimpleName ++ "(" ++ context.cwd.string ++ ")" + override def show = this.getClass.getSimpleName ++ "(" ++ context.projectDirectory.string ++ ")" final override lazy val logger = context.logger final override lazy val lib: Lib = new Lib(logger) private val root = lib.loadRoot( context.copy(args=Seq()) ) @@ -31,7 +31,7 @@ case class BuildDependency(context: Context) extends TriggerLoop{ } /* case class DependencyOr(first: BuildDependency, second: JavaDependency) extends ProjectProxy with BuildDependencyBase{ - val isFirst = new File(first.context.cwd).exists + val isFirst = new File(first.context.projectDirectory).exists def triggerLoopFiles = if(isFirst) first.triggerLoopFiles else Seq() protected val delegate = if(isFirst) first else second } diff --git a/stage2/GitDependency.scala b/stage2/GitDependency.scala index 27bf253..16423df 100644 --- a/stage2/GitDependency.scala +++ b/stage2/GitDependency.scala @@ -38,7 +38,7 @@ case class GitDependency( } val managedBuild = lib.loadDynamic( - context.copy( cwd = checkoutDirectory, args = Seq() ) + context.copy( projectDirectory = checkoutDirectory, args = Seq() ) ) Seq( managedBuild ) } diff --git a/stage2/Lib.scala b/stage2/Lib.scala index 6b6263c..dbd6108 100644 --- a/stage2/Lib.scala +++ b/stage2/Lib.scala @@ -34,19 +34,19 @@ final class Lib(logger: Logger) extends Stage1Lib(logger) with Scaffold{ This can either the Build itself, of if exists a BuildBuild or a BuildBuild for a BuildBuild and so on. */ def loadRoot(context: Context, default: Context => Build = new Build(_)): Build = { - context.logger.composition( context.logger.showInvocation("Build.loadRoot",context) ) - def findStartDir(cwd: File): File = { - val buildDir = realpath( cwd ++ "/build" ) - if(buildDir.exists) findStartDir(buildDir) else cwd + context.logger.composition( context.logger.showInvocation("Build.loadRoot",context.projectDirectory) ) + def findStartDir(projectDirectory: File): File = { + val buildDir = realpath( projectDirectory ++ "/build" ) + if(buildDir.exists) findStartDir(buildDir) else projectDirectory } - val start = findStartDir(context.cwd) + val start = findStartDir(context.projectDirectory) - val useBasicBuildBuild = context.cwd == start + val useBasicBuildBuild = context.projectDirectory == start val rootBuildClassName = if( useBasicBuildBuild ) buildBuildClassName else buildClassName try{ - if(useBasicBuildBuild) default( context ) else new cbt.BuildBuild( context.copy( cwd = start ) ) + if(useBasicBuildBuild) default( context ) else new cbt.BuildBuild( context.copy( projectDirectory = start ) ) } catch { case e:ClassNotFoundException if e.getMessage == rootBuildClassName => throw new Exception(s"no class $rootBuildClassName found in " ++ start.string) @@ -110,7 +110,7 @@ final class Lib(logger: Logger) extends Stage1Lib(logger) with Scaffold{ logger.lib(s"invoke testDefault( $context )") val exitCode: ExitCode = loadDynamic( - context.copy( cwd = context.cwd ++ "/test", args = loggerArg.toVector ++ context.args ), + context.copy( projectDirectory = context.projectDirectory ++ "/test", args = loggerArg.toVector ++ context.args ), new Build(_) with mixins.Test ).run logger.lib(s"return testDefault( $context )") @@ -145,7 +145,7 @@ final class Lib(logger: Logger) extends Stage1Lib(logger) with Scaffold{ ( ( if( thisTasks.nonEmpty ){ - s"""Methods provided by Build ${context.cwd} + s"""Methods provided by Build ${context.projectDirectory} ${thisTasks.mkString(" ")} diff --git a/stage2/Stage2.scala b/stage2/Stage2.scala index 4ae149c..ddadfb6 100644 --- a/stage2/Stage2.scala +++ b/stage2/Stage2.scala @@ -22,8 +22,8 @@ object Stage2 extends Stage2Base{ 0 } val task = args.args.lift( taskIndex ) - - val context = Context( args.cwd, args.args.drop( taskIndex ), logger, args.cbtHasChanged, new ClassLoaderCache(logger) ) + + val context = Context( args.cwd, args.cwd, args.args.drop( taskIndex ), logger, args.cbtHasChanged, args.classLoaderCache ) val first = lib.loadRoot( context ) val build = first.finalBuild @@ -44,9 +44,8 @@ object Stage2 extends Stage2Base{ case file if triggerFiles.exists(file.toString startsWith _.toString) => val build = lib.loadDynamic(context) - val reflectBuild = new lib.ReflectBuild( build ) logger.loop(s"Re-running $task for " ++ build.projectDirectory.toString) - reflectBuild.callNullary(task) + new lib.ReflectBuild(build).callNullary(task) } } else { new lib.ReflectBuild(build).callNullary(task) diff --git a/test/test.scala b/test/test.scala index 7bd2c6a..29e6afa 100644 --- a/test/test.scala +++ b/test/test.scala @@ -72,7 +72,7 @@ object Main{ logger.test( "Running tests " ++ _args.toList.toString ) { - val noContext = Context(cbtHome ++ "/test/nothing", Seq(), logger, false, new ClassLoaderCache(logger)) + val noContext = Context(cbtHome ++ "/test/nothing", cbtHome, Seq(), logger, false, new ClassLoaderCache(logger)) val b = new Build(noContext){ override def dependencies = Seq( MavenRepository.central.resolve( |