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 /stage2 | |
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 'stage2')
-rw-r--r-- | stage2/BasicBuild.scala | 55 | ||||
-rw-r--r-- | stage2/BuildBuild.scala | 3 | ||||
-rw-r--r-- | stage2/BuildDependency.scala | 5 | ||||
-rw-r--r-- | stage2/GitDependency.scala | 12 | ||||
-rw-r--r-- | stage2/Lib.scala | 8 | ||||
-rw-r--r-- | stage2/PackageJars.scala | 13 | ||||
-rw-r--r-- | stage2/Stage2.scala | 19 | ||||
-rw-r--r-- | stage2/ToolsStage2.scala | 5 | ||||
-rw-r--r-- | stage2/ToolsTasks.scala | 29 | ||||
-rw-r--r-- | stage2/plugins/Dotty.scala | 47 |
10 files changed, 89 insertions, 107 deletions
diff --git a/stage2/BasicBuild.scala b/stage2/BasicBuild.scala index efccfb4..750b0bc 100644 --- a/stage2/BasicBuild.scala +++ b/stage2/BasicBuild.scala @@ -9,6 +9,8 @@ trait BaseBuild extends BuildInterface with DependencyImplementation with Trigge // will create new instances given the context, which means operations in the // overrides will happen multiple times and if they are not idempotent stuff likely breaks def context: Context + def moduleKey: String = "BaseBuild("+projectDirectory.string+")" + implicit def transientCache: java.util.Map[AnyRef,AnyRef] = context.transientCache // library available to builds implicit protected final val logger: Logger = context.logger @@ -59,7 +61,7 @@ trait BaseBuild extends BuildInterface with DependencyImplementation with Trigge // FIXME: this should probably be removed Resolver( mavenCentral ).bind( "org.scala-lang" % "scala-library" % scalaVersion - ) + ) ++ ( if(localJars.nonEmpty) Seq( BinaryDependency(localJars, Nil) ) else Nil ) // ========== paths ========== final private val defaultSourceDirectory = projectDirectory ++ "/src" @@ -99,7 +101,7 @@ trait BaseBuild extends BuildInterface with DependencyImplementation with Trigge } logEmptySourceDirectories() - def Resolver( urls: URL* ) = MavenResolver( context.cbtHasChanged, context.paths.mavenCache, urls: _* ) + def Resolver( urls: URL* ) = MavenResolver( context.cbtLastModified, context.paths.mavenCache, urls: _* ) def ScalaDependency( groupId: String, artifactId: String, version: String, classifier: Classifier = Classifier.none, @@ -118,17 +120,16 @@ trait BaseBuild extends BuildInterface with DependencyImplementation with Trigge .flatMap(_.listFiles) .filter(_.toString.endsWith(".jar")) - override def dependencyClasspath : ClassPath = ClassPath(localJars) ++ super.dependencyClasspath + override def dependencyClasspath : ClassPath = super.dependencyClasspath - protected def compileDependencies: Seq[Dependency] = Nil - final def compileClasspath : ClassPath = - dependencyClasspath ++ ClassPath( compileDependencies.flatMap(_.exportedClasspath.files).distinct ) + protected def compileDependencies: Seq[Dependency] = dependencies + final def compileClasspath : ClassPath = Dependencies(compileDependencies).classpath def resourceClasspath: ClassPath = { val resourcesDirectory = projectDirectory ++ "/resources" ClassPath( if(resourcesDirectory.exists) Seq(resourcesDirectory) else Nil ) } - def exportedClasspath : ClassPath = ClassPath(compile.toSeq) ++ resourceClasspath + def exportedClasspath : ClassPath = ClassPath(compileFile.toSeq) ++ resourceClasspath def targetClasspath = ClassPath(Seq(compileTarget)) // ========== compile, run, test ========== @@ -139,26 +140,20 @@ trait BaseBuild extends BuildInterface with DependencyImplementation with Trigge "-unchecked" ) - private object needsUpdateCache extends Cache[Boolean] - def needsUpdate: Boolean = needsUpdateCache( - context.cbtHasChanged - || lib.needsUpdate( sourceFiles, compileStatusFile ) - || transitiveDependencies.filterNot(_ == context.parentBuild).exists(_.needsUpdate) - ) + final def lastModified: Long = compile.getOrElse(0L) - private object compileCache extends Cache[Option[File]] - def compile: Option[File] = compileCache{ + final def compileFile: Option[File] = compile.map(_ => compileTarget) + + def compile: Option[Long] = taskCache[BaseBuild]("_compile").memoize{ lib.compile( - context.cbtHasChanged, - needsUpdate || context.parentBuild.map(_.needsUpdate).getOrElse(false), - sourceFiles, compileTarget, compileStatusFile, compileClasspath, + context.cbtLastModified, + sourceFiles, compileTarget, compileStatusFile, compileDependencies, context.paths.mavenCache, scalacOptions, context.classLoaderCache, zincVersion = zincVersion, scalaVersion = scalaVersion ) } - - def mainClasses: Seq[Class[_]] = compile.toSeq.flatMap( lib.mainClasses( _, classLoader(classLoaderCache) ) ) + def mainClasses: Seq[Class[_]] = compileFile.toSeq.flatMap( lib.mainClasses( _, classLoader(classLoaderCache) ) ) def runClass: Option[String] = lib.runClass( mainClasses ).map( _.getName ) @@ -186,7 +181,7 @@ trait BaseBuild extends BuildInterface with DependencyImplementation with Trigge System.setProperty(colorized, "true") } - val scalac = new ScalaCompilerDependency(context.cbtHasChanged, context.paths.mavenCache, scalaVersion) + val scalac = new ScalaCompilerDependency(context.cbtLastModified, context.paths.mavenCache, scalaVersion) lib.runMain( "scala.tools.nsc.MainGenericRunner", Seq( @@ -268,24 +263,6 @@ trait BaseBuild extends BuildInterface with DependencyImplementation with Trigge def finalBuild: BuildInterface = this override def show = this.getClass.getSimpleName ++ "(" ++ projectDirectory.string ++ ")" - // TODO: allow people not provide the method name, maybe via macro - // TODO: pull this out into lib - /** - caches given value in context keyed with given key and projectDirectory - the context is fresh on every complete run of cbt - */ - def cached[T <: AnyRef](name: String)(task: => T): T = { - val cache = context.taskCache - val key = (projectDirectory,name) - if( cache.containsKey(key) ){ - cache.get(key).asInstanceOf[T] - } else{ - val value = task - cache.put( key, value ) - value - } - } - // a method that can be called only to trigger any side-effects final def `void` = () } diff --git a/stage2/BuildBuild.scala b/stage2/BuildBuild.scala index cf515bb..1b05214 100644 --- a/stage2/BuildBuild.scala +++ b/stage2/BuildBuild.scala @@ -32,8 +32,7 @@ trait BuildBuildWithoutEssentials extends BaseBuild{ override def dependencies = super.dependencies :+ context.cbtDependency def managedBuildDirectory: java.io.File = lib.realpath( projectDirectory.parent ) - private object managedBuildCache extends Cache[BuildInterface] - def managedBuild = managedBuildCache{ + def managedBuild = taskCache[BuildBuildWithoutEssentials]("managedBuild").memoize{ val managedBuildFile = projectDirectory++"/build.scala" logger.composition("Loading build at " ++ managedBuildDirectory.toString) val build = ( diff --git a/stage2/BuildDependency.scala b/stage2/BuildDependency.scala index 197a7a1..236f958 100644 --- a/stage2/BuildDependency.scala +++ b/stage2/BuildDependency.scala @@ -16,15 +16,18 @@ trait TriggerLoop extends DependencyImplementation{ } /** You likely want to use the factory method in the BasicBuild class instead of this. */ final case class DirectoryDependency(context: Context) extends TriggerLoop{ + override def toString = show override def show = this.getClass.getSimpleName ++ "(" ++ context.projectDirectory.string ++ ")" + def moduleKey = this.getClass.getName ++ "("+context.projectDirectory.string+")" lazy val logger = context.logger override lazy val lib: Lib = new Lib(logger) + def transientCache = context.transientCache private lazy val root = lib.loadRoot( context.copy(args=Seq()) ) lazy val build = root.finalBuild def exportedClasspath = ClassPath() def dependencies = Seq(build) def triggerLoopFiles = root.triggerLoopFiles - def needsUpdate = build.needsUpdate + def lastModified = build.lastModified def targetClasspath = ClassPath() } /* diff --git a/stage2/GitDependency.scala b/stage2/GitDependency.scala index 650fd09..059d650 100644 --- a/stage2/GitDependency.scala +++ b/stage2/GitDependency.scala @@ -15,15 +15,14 @@ case class GitDependency( )(implicit val logger: Logger, classLoaderCache: ClassLoaderCache, context: Context ) extends DependencyImplementation{ import GitDependency._ override def lib = new Lib(logger) - + def moduleKey = this.getClass.getName ++ "(" ++ url ++ subDirectory.map("/" ++ _).getOrElse("") ++ "#" ++ ref ++ ")" + def transientCache = context.transientCache // TODO: add support for authentication via ssh and/or https // See http://www.codeaffine.com/2014/12/09/jgit-authentication/ private val GitUrl( _, domain, path ) = url private val credentialsFile = context.projectDirectory ++ "/git.login" - private object checkoutCache extends Cache[File] - private def authenticate(_git: CloneCommand) = if(!credentialsFile.exists){ _git @@ -36,7 +35,7 @@ case class GitDependency( _git.setCredentialsProvider( new UsernamePasswordCredentialsProvider(user, password) ) } - def checkout: File = checkoutCache{ + def checkout: File = taskCache[GitDependency]("checkout").memoize{ val checkoutDirectory = context.cache ++ s"/git/$domain/$path/$ref" val _git = if(checkoutDirectory.exists){ logger.git(s"Found existing checkout of $url#$ref in $checkoutDirectory") @@ -65,8 +64,7 @@ case class GitDependency( assert( actualRef == ref, s"actual ref '$actualRef' does not match expected ref '$ref'") checkoutDirectory } - private object dependencyCache extends Cache[DependencyImplementation] - def dependency = dependencyCache{ + def dependency = taskCache[GitDependency]("dependency").memoize{ DirectoryDependency( context.copy( projectDirectory = checkout ++ subDirectory.map("/" ++ _).getOrElse("") @@ -78,5 +76,5 @@ case class GitDependency( def exportedClasspath = ClassPath() private[cbt] def targetClasspath = exportedClasspath - def needsUpdate: Boolean = false + def lastModified: Long = dependency.lastModified } diff --git a/stage2/Lib.scala b/stage2/Lib.scala index ca85555..45803b4 100644 --- a/stage2/Lib.scala +++ b/stage2/Lib.scala @@ -15,7 +15,7 @@ import scala.util._ case class Developer(id: String, name: String, timezone: String, url: URL) /** Don't extend. Create your own libs :). */ -final class Lib(logger: Logger) extends Stage1Lib(logger) with Scaffold{ +final class Lib(val logger: Logger) extends Stage1Lib(logger) with Scaffold{ lib => val buildClassName = "Build" @@ -74,7 +74,7 @@ final class Lib(logger: Logger) extends Stage1Lib(logger) with Scaffold{ } def docJar( - cbtHasChanged: Boolean, + cbtLastModified: Long, scalaVersion: String, sourceFiles: Seq[File], dependencyClasspath: ClassPath, @@ -86,7 +86,7 @@ final class Lib(logger: Logger) extends Stage1Lib(logger) with Scaffold{ compileArgs: Seq[String], classLoaderCache: ClassLoaderCache, mavenCache: File - ): Option[File] = { + )(implicit transientCache: java.util.Map[AnyRef,AnyRef]): Option[File] = { if(sourceFiles.isEmpty){ None } else { @@ -101,7 +101,7 @@ final class Lib(logger: Logger) extends Stage1Lib(logger) with Scaffold{ runMain( "scala.tools.nsc.ScalaDoc", args, - ScalaDependencies(cbtHasChanged,mavenCache,scalaVersion)(logger).classLoader(classLoaderCache) + new ScalaDependencies(cbtLastModified,mavenCache,scalaVersion).classLoader(classLoaderCache) ) } lib.jarFile( diff --git a/stage2/PackageJars.scala b/stage2/PackageJars.scala index ff89284..7079786 100644 --- a/stage2/PackageJars.scala +++ b/stage2/PackageJars.scala @@ -10,20 +10,17 @@ trait PackageJars extends BaseBuild with ArtifactInfo{ Seq(() => jar, () => docJar, () => srcJar) )( _() ).flatten - private object cacheJarBasicBuild extends Cache[Option[File]] - def jar: Option[File] = cacheJarBasicBuild{ - compile.flatMap( lib.jar( artifactId, scalaMajorVersion, version, _, jarTarget ) ) + def jar: Option[File] = taskCache[PackageJars]("jar").memoize{ + compileFile.flatMap( lib.jar( artifactId, scalaMajorVersion, version, _, jarTarget ) ) } - private object cacheSrcJarBasicBuild extends Cache[Option[File]] - def srcJar: Option[File] = cacheSrcJarBasicBuild{ + def srcJar: Option[File] = taskCache[PackageJars]("srcJar").memoize{ lib.srcJar( sourceFiles, artifactId, scalaMajorVersion, version, scalaTarget ) } - private object cacheDocBasicBuild extends Cache[Option[File]] - def docJar: Option[File] = cacheDocBasicBuild{ + def docJar: Option[File] = taskCache[PackageJars]("docJar").memoize{ lib.docJar( - context.cbtHasChanged, + context.cbtLastModified, scalaVersion, sourceFiles, compileClasspath, docTarget, jarTarget, artifactId, scalaMajorVersion, version, scalacOptions, context.classLoaderCache, context.paths.mavenCache diff --git a/stage2/Stage2.scala b/stage2/Stage2.scala index dfbead3..0f5b557 100644 --- a/stage2/Stage2.scala +++ b/stage2/Stage2.scala @@ -1,12 +1,13 @@ package cbt import java.io._ +import java.util._ object Stage2 extends Stage2Base{ def getBuild(context: Context) = { new Lib( context.logger ).loadRoot( context ).finalBuild } - def run( args: Stage2Args ): Unit = { + def run( args: Stage2Args ): ExitCode = { import args.logger val paths = CbtPaths(args.cbtHome,args.cache) import paths._ @@ -22,18 +23,17 @@ object Stage2 extends Stage2Base{ 0 } val task = args.args.lift( taskIndex ) - - val context: Context = ContextImplementation( + + val context: Context = new ContextImplementation( args.cwd, args.cwd, args.args.drop( taskIndex +1 ).toArray, logger.enabledLoggers.toArray, logger.start, - args.cbtHasChanged, + args.stage2LastModified, null, - args.permanentKeys, - args.permanentClassLoaders, - new java.util.concurrent.ConcurrentHashMap, + args.classLoaderCache.hashMap, + args.transientCache, args.cache, args.cbtHome, args.cbtHome, @@ -75,10 +75,13 @@ object Stage2 extends Stage2Base{ logger.loop(s"Re-running $task for " ++ build.show) call(build) } + ExitCode.Success } else { val code = call(build) logger.stage2(s"Stage2 end") - System.exit(code.integer) + code } + + res } } diff --git a/stage2/ToolsStage2.scala b/stage2/ToolsStage2.scala index df615fc..2899173 100644 --- a/stage2/ToolsStage2.scala +++ b/stage2/ToolsStage2.scala @@ -1,12 +1,13 @@ package cbt import java.io._ object ToolsStage2 extends Stage2Base{ - def run( _args: Stage2Args ): Unit = { + def run( _args: Stage2Args ): ExitCode = { val args = _args.args.dropWhile(Seq("tools","direct") contains _) val lib = new Lib(_args.logger) - val toolsTasks = new ToolsTasks(lib, args, _args.cwd, _args.classLoaderCache, _args.cache, _args.cbtHome, _args.cbtHasChanged) + val toolsTasks = new ToolsTasks(lib, args, _args.cwd, _args.classLoaderCache, _args.cache, _args.cbtHome, _args.stage2LastModified) new lib.ReflectObject(toolsTasks){ def usage: String = "Available methods: " ++ lib.taskNames(toolsTasks.getClass).mkString(" ") }.callNullary(args.lift(0)) + ExitCode.Success } } diff --git a/stage2/ToolsTasks.scala b/stage2/ToolsTasks.scala index 975b49a..6acf72c 100644 --- a/stage2/ToolsTasks.scala +++ b/stage2/ToolsTasks.scala @@ -9,12 +9,13 @@ class ToolsTasks( classLoaderCache: ClassLoaderCache, cache: File, cbtHome: File, - cbtHasChanged: Boolean + cbtLastModified: Long ){ private val paths = CbtPaths(cbtHome, cache) import paths._ - private def Resolver( urls: URL* ) = MavenResolver(cbtHasChanged,mavenCache,urls: _*) + private def Resolver( urls: URL* ) = MavenResolver(cbtLastModified,mavenCache,urls: _*) implicit val logger: Logger = lib.logger + implicit val transientCache: java.util.Map[AnyRef,AnyRef] = new java.util.HashMap def createMain: Unit = lib.createMain( cwd ) def createBuild: Unit = lib.createBuild( cwd ) def gui = NailgunLauncher.main(Array( @@ -55,7 +56,7 @@ class ToolsTasks( } def scala = { val version = args.lift(1).getOrElse(constants.scalaVersion) - val scalac = new ScalaCompilerDependency( cbtHasChanged, mavenCache, version ) + val scalac = new ScalaCompilerDependency( cbtLastModified, mavenCache, version ) val _args = Seq("-cp", scalac.classpath.string) ++ args.drop(2) lib.runMain( "scala.tools.nsc.MainGenericRunner", _args, scalac.classLoader(classLoaderCache) @@ -96,14 +97,15 @@ class ToolsTasks( val n = valName(d) s""" // ${d.groupId}:${d.artifactId}:${d.version} - download(new URL(mavenUrl + "${d.basePath(true)}.jar"), Paths.get(${n}File), "${d.jarSha1}"); - String[] ${n}ClasspathArray = new String[]{${deps.sortBy(_.jar).map(valName(_)+"File").mkString(", ")}}; - String ${n}Classpath = classpath( ${n}ClasspathArray ); - ClassLoader $n = - classLoaderCache.contains( ${n}Classpath ) - ? classLoaderCache.get( ${n}Classpath ) - : classLoaderCache.put( classLoader( ${n}File, $parentString ), ${n}Classpath );""" + ClassLoader $n = loadDependency( + mavenUrl + "${d.basePath(true)}.jar", + ${n}File, + "${d.jarSha1}", + classLoaderCache, + $parentString, + ${n}ClasspathArray + );""" } } val assignments = codeEach(zinc) ++ codeEach(scalaXml) @@ -115,6 +117,7 @@ import java.io.*; import java.nio.file.*; import java.net.*; import java.security.*; +import java.util.*; import static cbt.Stage0Lib.*; import static cbt.NailgunLauncher.*; @@ -129,13 +132,13 @@ class EarlyDependencies{ ${files.map(d => s""" String ${valName(d)}File;""").mkString("\n")} public EarlyDependencies( - String mavenCache, String mavenUrl, ClassLoaderCache2<ClassLoader> classLoaderCache, ClassLoader rootClassLoader - ) throws Exception { + String mavenCache, String mavenUrl, ClassLoaderCache classLoaderCache, ClassLoader rootClassLoader + ) throws Throwable { ${files.map(d => s""" ${valName(d)}File = mavenCache + "${d.basePath(true)}.jar";""").mkString("\n")} ${scalaDeps.map(d => s""" download(new URL(mavenUrl + "${d.basePath(true)}.jar"), Paths.get(${valName(d)}File), "${d.jarSha1}");""").mkString("\n")} ${assignments.mkString("\n")} - + classLoader = scalaXml_${scalaXmlVersion.replace(".","_")}_; classpathArray = scalaXml_${scalaXmlVersion.replace(".","_")}_ClasspathArray; diff --git a/stage2/plugins/Dotty.scala b/stage2/plugins/Dotty.scala index 8671fb6..6fe5dd3 100644 --- a/stage2/plugins/Dotty.scala +++ b/stage2/plugins/Dotty.scala @@ -8,18 +8,15 @@ trait Dotty extends BaseBuild{ def dottyVersion: String = "0.1-20160926-ec28ea1-NIGHTLY" def dottyOptions: Seq[String] = Seq() override def scalaTarget: File = target ++ s"/dotty-$dottyVersion" - + private lazy val dottyLib = new DottyLib( - logger, context.cbtHasChanged, context.paths.mavenCache, + logger, context.cbtLastModified, context.paths.mavenCache, context.classLoaderCache, dottyVersion = dottyVersion ) - private object compileCache extends Cache[Option[File]] - override def compile: Option[File] = compileCache{ + override def compile: Option[Long] = taskCache[Dotty]("compile").memoize{ dottyLib.compile( - needsUpdate || context.parentBuild.map(_.needsUpdate).getOrElse(false), - sourceFiles, compileTarget, compileStatusFile, compileClasspath, - dottyOptions + sourceFiles, compileTarget, compileStatusFile, compileDependencies, dottyOptions ) } @@ -37,15 +34,15 @@ trait Dotty extends BaseBuild{ class DottyLib( logger: Logger, - cbtHasChanged: Boolean, + cbtLastModified: Long, mavenCache: File, classLoaderCache: ClassLoaderCache, dottyVersion: String -){ +)(implicit transientCache: java.util.Map[AnyRef,AnyRef]){ val lib = new Lib(logger) import lib._ - private def Resolver(urls: URL*) = MavenResolver(cbtHasChanged, mavenCache, urls: _*) + private def Resolver(urls: URL*) = MavenResolver(cbtLastModified, mavenCache, urls: _*) private lazy val dottyDependency = Resolver(mavenCentral).bindOne( MavenDependency("ch.epfl.lamp","dotty_2.11",dottyVersion) ) @@ -95,22 +92,24 @@ class DottyLib( } def compile( - needsRecompile: Boolean, - files: Seq[File], + sourceFiles: Seq[File], compileTarget: File, statusFile: File, - classpath: ClassPath, + dependencies: Seq[Dependency], dottyOptions: Seq[String] - ): Option[File] = { - + ): 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 ){ - val start = System.currentTimeMillis + val start = System.currentTimeMillis + val lastCompiled = statusFile.lastModified + if( d.lastModified > lastCompiled || sourceFiles.exists(_.lastModified > lastCompiled) ){ val _class = "dotty.tools.dotc.Main" val dualArgs = @@ -119,7 +118,7 @@ class DottyLib( ) val singleArgs = dottyOptions.map( "-S" ++ _ ) - val code = + val code = try{ System.err.println("Compiling with Dotty to " ++ compileTarget.toString) compileTarget.mkdirs @@ -129,7 +128,7 @@ class DottyLib( dualArgs ++ singleArgs ++ Seq( "-bootclasspath", dottyDependency.classpath.string, // let's put cp last. It so long "-classpath", classpath.string // let's put cp last. It so long - ) ++ files.map(_.toString), + ) ++ sourceFiles.map(_.toString), dottyDependency.classLoader(classLoaderCache) ) } @@ -151,7 +150,7 @@ ${dottyDependency.classpath.strings.mkString(":\\\n")} \\ -classpath \\ ${classpath.strings.mkString(":\\\n")} \\ \\ -${files.sorted.mkString(" \\\n")} +${sourceFiles.sorted.mkString(" \\\n")} """ ) ExitCode.Failure @@ -165,8 +164,10 @@ ${files.sorted.mkString(" \\\n")} } 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 ) } } } |