aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xcbt6
-rw-r--r--stage1/Stage1.scala26
-rw-r--r--stage1/Stage1Lib.scala106
-rw-r--r--stage1/logger.scala2
-rw-r--r--stage2/DefaultBuild.scala13
-rw-r--r--stage2/Lib.scala41
-rw-r--r--stage2/Stage2.scala43
-rw-r--r--stage2/mixins.scala2
-rw-r--r--test/test.scala31
9 files changed, 164 insertions, 106 deletions
diff --git a/cbt b/cbt
index 7e721c6..d9f1ac7 100755
--- a/cbt
+++ b/cbt
@@ -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)
}
}