diff options
-rwxr-xr-x | cbt | 6 | ||||
-rw-r--r-- | stage1/Stage1.scala | 26 | ||||
-rw-r--r-- | stage1/Stage1Lib.scala | 106 | ||||
-rw-r--r-- | stage1/logger.scala | 2 | ||||
-rw-r--r-- | stage2/DefaultBuild.scala | 13 | ||||
-rw-r--r-- | stage2/Lib.scala | 41 | ||||
-rw-r--r-- | stage2/Stage2.scala | 43 | ||||
-rw-r--r-- | stage2/mixins.scala | 2 | ||||
-rw-r--r-- | test/test.scala | 31 |
9 files changed, 164 insertions, 106 deletions
@@ -227,6 +227,7 @@ stage1 () { log "Running $mainClass via Nailgun." $* $NG cbt.NailgunLauncher $mainClass $CP "$CWD" $* fi + exitCode=$? log "Done running $mainClass." $* } @@ -238,4 +239,9 @@ while true; do echo "======= Restarting CBT =======" 1>&2 done +if [ $compiles -ne 0 ] || [ $compiles2 -ne 0 ]; then + exitCode=1 +fi + log "Exiting CBT" $* +exit $exitCode diff --git a/stage1/Stage1.scala b/stage1/Stage1.scala index 5391e2d..8db12cf 100644 --- a/stage1/Stage1.scala +++ b/stage1/Stage1.scala @@ -14,7 +14,7 @@ object CheckAlive{ } } -private[cbt] class Init(args: Array[String]) { +class Init(args: Array[String]) { /** * Raw parameters including their `-D` flag. **/ @@ -35,8 +35,6 @@ private[cbt] class Init(args: Array[String]) { }).toMap ++ System.getProperties.asScala val logger = new Logger(props.get("log")) - - val cwd = argsV(0) } object Stage1 extends Stage1Base{ @@ -57,25 +55,27 @@ abstract class Stage1Base{ def main(args: Array[String]): Unit = { val init = new Init(args) val lib = new Stage1Lib(init.logger) + import lib._ - lib.logger.stage1(s"[$now] Stage1 start") - lib.logger.stage1("Stage1: after creating lib") + logger.stage1(s"[$now] Stage1 start") + logger.stage1("Stage1: after creating lib") val cwd = args(0) val src = stage2.listFiles.toVector.filter(_.isFile).filter(_.toString.endsWith(".scala")) val changeIndicator = new File(stage2Target+"/cbt/Build.class") - lib.logger.stage1("before conditionally running zinc to recompile CBT") + logger.stage1("before conditionally running zinc to recompile CBT") if( src.exists(newerThan(_, changeIndicator)) ) { - val stage1Classpath = CbtDependency(init.logger).dependencyClasspath - lib.logger.stage1("cbt.lib has changed. Recompiling with cp: "+stage1Classpath) - lib.zinc( true, src, stage2Target, stage1Classpath )( zincVersion = "0.3.9", scalaVersion = constants.scalaVersion ) + val stage1Classpath = CbtDependency(logger).dependencyClasspath + logger.stage1("cbt.lib has changed. Recompiling with cp: "+stage1Classpath) + zinc( true, src, stage2Target, stage1Classpath )( zincVersion = "0.3.9", scalaVersion = constants.scalaVersion ) } - lib.logger.stage1(s"[$now] calling CbtDependency.classLoader") + logger.stage1(s"[$now] calling CbtDependency.classLoader") - lib.logger.stage1(s"[$now] Run Stage2") - lib.runMain( mainClass, cwd +: args.drop(1).toVector, CbtDependency(init.logger).classLoader ) - lib.logger.stage1(s"[$now] Stage1 end") + logger.stage1(s"[$now] Run Stage2") + val ExitCode(exitCode) = runMain( mainClass, cwd +: args.drop(1).toVector, CbtDependency(logger).classLoader ) + logger.stage1(s"[$now] Stage1 end") + System.exit(exitCode) } } diff --git a/stage1/Stage1Lib.scala b/stage1/Stage1Lib.scala index e1b9d3d..ac77a92 100644 --- a/stage1/Stage1Lib.scala +++ b/stage1/Stage1Lib.scala @@ -3,6 +3,7 @@ package cbt import cbt.paths._ import java.io._ +import java.lang.reflect.InvocationTargetException import java.net._ import java.nio.file._ import javax.tools._ @@ -12,6 +13,24 @@ import javax.xml.bind.annotation.adapters.HexBinaryAdapter import scala.collection.immutable.Seq +// CLI interop +case class ExitCode(code: Int) +object ExitCode{ + val Success = ExitCode(0) + val Failure = ExitCode(1) +} + +class TrappedExitCode(private val exitCode: Int) extends Exception +object TrappedExitCode{ + def unapply(e: Throwable): Option[ExitCode] = + Option(e) flatMap { + case i: InvocationTargetException => unapply(i.getTargetException) + case e: TrappedExitCode => Some( ExitCode(e.exitCode) ) + case _ => None + } +} + + case class Context( cwd: String, args: Seq[String], logger: Logger ) case class ClassPath(files: Seq[File]){ @@ -97,16 +116,20 @@ class Stage1Lib( val logger: Logger ){ // ========== compilation / execution ========== - def runMainIfFound(cls: String, args: Seq[String], classLoader: ClassLoader ){ - if( classLoader.canLoad(cls) ) runMain(cls: String, args: Seq[String], classLoader: ClassLoader ) + def runMainIfFound(cls: String, args: Seq[String], classLoader: ClassLoader ): ExitCode = { + if( classLoader.canLoad(cls) ){ + runMain(cls, args, classLoader ) + } else ExitCode.Success } - def runMain(cls: String, args: Seq[String], classLoader: ClassLoader ){ + def runMain(cls: String, args: Seq[String], classLoader: ClassLoader ): ExitCode = { logger.lib(s"Running $cls.main($args) with classLoader: "+classLoader) - classLoader - .loadClass(cls) - .getMethod( "main", scala.reflect.classTag[Array[String]].runtimeClass ) - .invoke( null, args.toArray.asInstanceOf[AnyRef] ); + trapExitCode{ + classLoader + .loadClass(cls) + .getMethod( "main", scala.reflect.classTag[Array[String]].runtimeClass ) + .invoke( null, args.toArray.asInstanceOf[AnyRef] ) + } } implicit class ClassLoaderExtensions(classLoader: ClassLoader){ @@ -116,7 +139,7 @@ class Stage1Lib( val logger: Logger ){ true } catch { case e: ClassNotFoundException => false - } + } } } @@ -152,25 +175,33 @@ class Stage1Lib( val logger: Logger ){ val scalaReflect = MavenDependency("org.scala-lang","scala-reflect",scalaVersion)(logger).jar val scalaCompiler = MavenDependency("org.scala-lang","scala-compiler",scalaVersion)(logger).jar - redirectOutToErr{ - lib.runMain( - "com.typesafe.zinc.Main", - Seq( - "-scala-compiler", scalaCompiler.toString, - "-scala-library", scalaLibrary.toString, - "-sbt-interface", sbtInterface.toString, - "-compiler-interface", compilerInterface.toString, - "-scala-extra", scalaReflect.toString, - "-cp", cp, - "-d", compileTarget.toString - ) ++ extraArgs.map("-S"+_) ++ files.map(_.toString), - zinc.classLoader - ) + val code = redirectOutToErr{ + trapExitCode{ + lib.runMain( + "com.typesafe.zinc.Main", + Seq( + "-scala-compiler", scalaCompiler.toString, + "-scala-library", scalaLibrary.toString, + "-sbt-interface", sbtInterface.toString, + "-compiler-interface", compilerInterface.toString, + "-scala-extra", scalaReflect.toString, + "-cp", cp, + "-d", compileTarget.toString + ) ++ extraArgs.map("-S"+_) ++ files.map(_.toString), + zinc.classLoader + ) + } + } + if(code != ExitCode.Success){ + // FIXME: zinc currently always returns exit code 0 + // hack that triggers recompilation next time. Nicer solution? + val now = System.currentTimeMillis() + files.foreach{_.setLastModified(now)} } } } - def redirectOutToErr[T](code: => T): Unit = { + def redirectOutToErr[T](code: => T): T = { val oldOut = System.out try{ System.setOut(System.err) @@ -180,5 +211,34 @@ class Stage1Lib( val logger: Logger ){ } } + def trapExitCode( code: => Unit ): ExitCode = { + val old: Option[SecurityManager] = Option(System.getSecurityManager()) + try{ + val securityManager = new SecurityManager{ + override def checkPermission( permission: Permission ) = { + /* + NOTE: is it actually ok, to just make these empty? + Calling .super leads to ClassNotFound exteption for a lambda. + Calling to the previous SecurityManager leads to a stack overflow + */ + } + override def checkPermission( permission: Permission, context: Any ) = { + /* Does this methods need to be overidden? */ + } + override def checkExit( status: Int ) = { + super.checkExit(status) + logger.lib(s"checkExit($status)") + throw new TrappedExitCode(status) + } + } + System.setSecurityManager( securityManager ) + code + ExitCode.Success + } catch { + case TrappedExitCode(exitCode) => exitCode + } finally { + System.setSecurityManager(old.getOrElse(null)) + } + } } diff --git a/stage1/logger.scala b/stage1/logger.scala index eaf64db..ecc1579 100644 --- a/stage1/logger.scala +++ b/stage1/logger.scala @@ -28,6 +28,7 @@ case class Logger(enabledLoggers: Set[String]) { final def composition(msg: => String) = logGuarded(names.composition, msg) final def resolver(msg: => String) = logGuarded(names.resolver, msg) final def lib(msg: => String) = logGuarded(names.lib, msg) + final def test(msg: => String) = logGuarded(names.test, msg) private object names{ val stage1 = "stage1" @@ -37,6 +38,7 @@ case class Logger(enabledLoggers: Set[String]) { val resolver = "resolver" val composition = "composition" val lib = "lib" + val test = "test" } private def logGuarded(name: String, msg: => String) = { diff --git a/stage2/DefaultBuild.scala b/stage2/DefaultBuild.scala index c0072ff..c98aea0 100644 --- a/stage2/DefaultBuild.scala +++ b/stage2/DefaultBuild.scala @@ -85,7 +85,7 @@ class Build(val context: Context) extends Dependency with TriggerLoop{ final val logger = context.logger override final protected val lib: Lib = new Lib(logger) // ========== general stuff ========== - + def enableConcurrency = false final def projectDirectory: File = new File(context.cwd) assert( projectDirectory.exists, "projectDirectory does not exist: "+projectDirectory ) @@ -104,7 +104,7 @@ class Build(val context: Context) extends Dependency with TriggerLoop{ def dependencies: Seq[Dependency] = Seq( "org.scala-lang" % "scala-library" % scalaVersion ) - + // ========== paths ========== final private val defaultSourceDirectory = new File(projectDirectory+"/src/") @@ -122,7 +122,7 @@ class Build(val context: Context) extends Dependency with TriggerLoop{ /** Source directories and files. Defaults to .scala and .java files in src/ and top-level. */ def sources: Seq[File] = Seq(defaultSourceDirectory) ++ projectDirectory.listFiles.toVector.filter(sourceFileFilter) - /** Which file endings to consider being source files. */ + /** Which file endings to consider being source files. */ def sourceFileFilter(file: File): Boolean = file.toString.endsWith(".scala") || file.toString.endsWith(".java") /** Absolute path names for all individual files found in sources directly or contained in directories. */ @@ -130,7 +130,7 @@ class Build(val context: Context) extends Dependency with TriggerLoop{ base <- sources.filter(_.exists).map(lib.realpath) file <- lib.listFilesRecursive(base) if file.isFile && sourceFileFilter(file) } yield file - + protected def assertSourceDirectories(): Unit = { val nonExisting = sources @@ -215,15 +215,16 @@ class Build(val context: Context) extends Dependency with TriggerLoop{ } def runClass: String = "Main" - def run: Unit = lib.runMainIfFound( runClass, Seq(), classLoader ) + def run: ExitCode = lib.runMainIfFound( runClass, context.args, classLoader ) - def test: Unit = lib.test(context) + def test: ExitCode = lib.test(context) context.logger.composition(">"*80) context.logger.composition("class "+this.getClass) context.logger.composition("dir "+context.cwd) context.logger.composition("sources "+sources.toList.mkString(" ")) context.logger.composition("target "+target) + context.logger.composition("context "+context) context.logger.composition("dependencyTree\n"+dependencyTree) context.logger.composition("<"*80) diff --git a/stage2/Lib.scala b/stage2/Lib.scala index b92e0b3..9971b55 100644 --- a/stage2/Lib.scala +++ b/stage2/Lib.scala @@ -15,6 +15,7 @@ import scala.util._ import ammonite.ops.{cwd => _,_} +// pom model case class Developer(id: String, name: String, timezone: String, url: URL) case class License(name: String, url: URL) @@ -87,17 +88,6 @@ final class Lib(logger: Logger) extends Stage1Lib(logger) with Scaffold{ version: String, compileArgs: Seq[String] ): File = { - class DisableSystemExit extends Exception - object DisableSystemExit{ - def apply(e: Throwable): Boolean = { - e match { - case i: InvocationTargetException => apply(i.getTargetException) - case _: DisableSystemExit => true - case _ => false - } - } - } - // FIXME: get this dynamically somehow, or is this even needed? val javacp = ClassPath( "/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/ext/cldrdata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/ext/dnsns.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/ext/jaccess.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/ext/jfxrt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/ext/localedata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/ext/nashorn.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/ext/sunec.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/ext/sunjce_provider.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/ext/sunpkcs11.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/ext/zipfs.jar:/System/Library/Java/Extensions/MRJToolkit.jar".split(":").toVector.map(new File(_)) @@ -106,14 +96,7 @@ final class Lib(logger: Logger) extends Stage1Lib(logger) with Scaffold{ mkdir(Path(apiTarget)) if(sourceFiles.nonEmpty){ System.err.println("creating docs") - try{ - System.setSecurityManager( - new SecurityManager{ - override def checkPermission( permission: java.security.Permission ) = { - if( permission.getName.startsWith("exitVM") ) throw new DisableSystemExit - } - } - ) + trapExitCode{ redirectOutToErr{ runMain( "scala.tools.nsc.ScalaDoc", @@ -128,10 +111,6 @@ final class Lib(logger: Logger) extends Stage1Lib(logger) with Scaffold{ ) ) } - } catch { - case e:InvocationTargetException if DisableSystemExit(e) => - } finally { - System.setSecurityManager(null) } } val docJar = new File(jarTarget+"/"+artifactId+"-"+version+"-javadoc.jar") @@ -139,14 +118,19 @@ final class Lib(logger: Logger) extends Stage1Lib(logger) with Scaffold{ docJar } - def test( context: Context ) = { + def test( context: Context ): ExitCode = { + val loggers = logger.enabledLoggers.mkString(",") + // FIXME: this is a hack to pass logger args on to the tests. + // should probably have a more structured way + val loggerArg = if(loggers != "") Some("-Dlog="+loggers) else None + logger.lib(s"invoke testDefault( $context )") - loadDynamic( - context.copy( cwd = context.cwd+"/test/" ), + val exitCode: ExitCode = loadDynamic( + context.copy( cwd = context.cwd+"/test/", args = loggerArg.toVector ++ context.args ), new Build(_) with mixins.Test ).run - logger.lib(s"return testDefault( $context )") - + logger.lib(s"return testDefault( $context )") + exitCode } // task reflection helpers @@ -203,6 +187,7 @@ final class Lib(logger: Logger) extends Stage1Lib(logger) with Scaffold{ case e:NoSuchMethodException if e.getMessage contains "toConsole" => result match { case () => "" + case ExitCode(code) => System.exit(code) case other => println( other.toString ) // no method .toConsole, using to String } } diff --git a/stage2/Stage2.scala b/stage2/Stage2.scala index f3e833f..c6783d4 100644 --- a/stage2/Stage2.scala +++ b/stage2/Stage2.scala @@ -10,7 +10,7 @@ import cbt.paths._ object Stage2{ - def main(args: Array[String]) = { + def main(args: Array[String]): Unit = { val init = new Init(args) import init._ @@ -27,32 +27,33 @@ object Stage2{ } val task = argsV.lift( taskIndex ) - val context = Context( cwd, argsV.drop( taskIndex + 1 ), logger ) + val context = Context( argsV(0), argsV.drop( taskIndex + 1 ), logger ) val first = lib.loadRoot( context ) val build = first.finalBuild - val res = if (loop) { - // TODO: this should allow looping over task specific files, like test files as well - val triggerFiles = first.triggerLoopFiles.map(lib.realpath) - val triggerCbtFiles = Seq( nailgun, stage1, stage2 ).map(lib.realpath _) - val allTriggerFiles = triggerFiles ++ triggerCbtFiles - - logger.loop("Looping change detection over:\n - "+allTriggerFiles.mkString("\n - ")) - - lib.watch(allTriggerFiles) { - case file if triggerCbtFiles.exists(file.toString startsWith _.toString) => - logger.loop("Change is in CBT's own source code.") - logger.loop("Restarting CBT.") - scala.util.control.Breaks.break - - case file if triggerFiles.exists(file.toString startsWith _.toString) => - new lib.ReflectBuild( lib.loadDynamic(context) ).callNullary(task) + val res = lib.trapExitCode{ + if (loop) { + // TODO: this should allow looping over task specific files, like test files as well + val triggerFiles = first.triggerLoopFiles.map(lib.realpath) + val triggerCbtFiles = Seq( nailgun, stage1, stage2 ).map(lib.realpath _) + val allTriggerFiles = triggerFiles ++ triggerCbtFiles + + logger.loop("Looping change detection over:\n - "+allTriggerFiles.mkString("\n - ")) + + lib.watch(allTriggerFiles) { + case file if triggerCbtFiles.exists(file.toString startsWith _.toString) => + logger.loop("Change is in CBT's own source code.") + logger.loop("Restarting CBT.") + scala.util.control.Breaks.break + + case file if triggerFiles.exists(file.toString startsWith _.toString) => + new lib.ReflectBuild( lib.loadDynamic(context) ).callNullary(task) + } + } else { + new lib.ReflectBuild(build).callNullary(task) } - } else { - new lib.ReflectBuild(build).callNullary(task) } init.logger.stage2(s"[$now] Stage2 end") - res } } diff --git a/stage2/mixins.scala b/stage2/mixins.scala index 5ed26d8..4d72325 100644 --- a/stage2/mixins.scala +++ b/stage2/mixins.scala @@ -23,7 +23,7 @@ trait ScalaTest extends Build with Test{ // workaround probable ScalaTest bug throwing away the outer classloader. Not caching doesn't nest them. override def cacheDependencyClassLoader = false - override def run = { + override def run: ExitCode = { val discoveryPath = compile.toString+"/" context.logger.lib("discoveryPath: "+discoveryPath) lib.runMain( diff --git a/test/test.scala b/test/test.scala index 9ab5b4e..3befa4a 100644 --- a/test/test.scala +++ b/test/test.scala @@ -1,3 +1,4 @@ +import cbt._ import cbt.paths._ import scala.collection.immutable.Seq @@ -5,7 +6,7 @@ object Main{ // micro framework var successes = 0 var failures = 0 - def assert(condition: Boolean, msg: String = null) = { + def assert(condition: Boolean, msg: String = "")(implicit logger: Logger) = { scala.util.Try{ Predef.assert(condition, msg) }.map{ _ => @@ -19,12 +20,14 @@ object Main{ }.get } - def runCbt(path: String, args: Seq[String]) = { + def runCbt(path: String, args: Seq[String])(implicit logger: Logger): Result = { import java.io._ - val allArgs = ((cbtHome + "/cbt") +: args) + val allArgs = ((cbtHome + "/cbt") +: args :+ "-Dlog=all") + logger.test(allArgs.toString) val pb = new ProcessBuilder( allArgs :_* ) pb.directory(new File(cbtHome + "/test/" + path)) - val p = pb.start + val p = pb.inheritIO.start + p.waitFor val berr = new BufferedReader(new InputStreamReader(p.getErrorStream)); val bout = new BufferedReader(new InputStreamReader(p.getInputStream)); p.waitFor @@ -34,32 +37,32 @@ object Main{ Result(out, err, p.exitValue == 0) } case class Result(out: String, err: String, exit0: Boolean) - def assertSuccess(res: Result) = { + def assertSuccess(res: Result)(implicit logger: Logger) = { assert(res.exit0,res.toString) } // tests - def usage(path: String) = { + def usage(path: String)(implicit logger: Logger) = { val usageString = "Methods provided by CBT" val res = runCbt(path, Seq()) - assert(res.out == "", res.out) + logger.test(res.toString) + assertSuccess(res) + assert(res.out == "", "#"+res.out+"#") assert(res.err contains usageString, res.err) } - def compile(path: String) = { + def compile(path: String)(implicit logger: Logger) = { val res = runCbt(path, Seq("compile")) assertSuccess(res) // assert(res.err == "", res.err) // FIXME: enable this } def main(args: Array[String]): Unit = { - import cbt._ - - println("Running tests ") + implicit val logger: Logger = new Init(args).logger + System.err.println("Running tests "+args.toList) usage("nothing") compile("nothing") { - val logger = new Logger(Set[String]()) val noContext = Context(cbtHome + "/test/" + "nothing",Seq(),logger) val b = new Build(noContext){ override def dependencies = Seq( @@ -71,8 +74,8 @@ object Main{ assert(cp.strings.distinct == cp.strings, "duplicates in classpath: "+cp) } - println(" DONE!") - println(successes+" succeeded, "+ failures+" failed" ) + System.err.println(" DONE!") + System.err.println(successes+" succeeded, "+ failures+" failed" ) if(failures > 0) System.exit(1) else System.exit(0) } } |