From 1f5242bac286d88a7ff9026a785653a3335fc999 Mon Sep 17 00:00:00 2001 From: Christopher Vogt Date: Sun, 6 Mar 2016 22:12:05 -0500 Subject: refactored and improved nailgun code in bash script. Should be easier to debug now. Doesn't wait endlessly. --- cbt | 61 ++++++++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 44 insertions(+), 17 deletions(-) (limited to 'cbt') diff --git a/cbt b/cbt index f66d2b8..d4d51f3 100755 --- a/cbt +++ b/cbt @@ -85,9 +85,29 @@ export TARGET=target/scala-2.11/classes/ mkdir -p $NAILGUN$TARGET mkdir -p $STAGE1$TARGET +nailgun_out=$NAILGUN/target/nailgun.stdout.log +nailgun_err=$NAILGUN/target/nailgun.strerr.log +foo(){ + while test $# -gt 0; do + case "$1" in + "-Dlog=nailgun") + nailgun_out=/dev/stderr + nailgun_err=/dev/stderr + ;; + "-Dlog=all") + nailgun_out=/dev/stderr + nailgun_err=/dev/stderr + ;; + esac + shift + done +} + +foo $@ + if [ "$1" = "kill" ]; then echo "Stopping nailgun" 1>&2 - $NG ng-stop >> $NAILGUN/target/nailgun.stdout.log 2>> $NAILGUN/target/nailgun.stderr.log & + $NG ng-stop >> $nailgun_out 2>> $nailgun_err & exit 1 fi @@ -104,10 +124,15 @@ else echo "(Note: nc not found. It will make slightly startup faster.)" 1>&2 fi -if [ $nc_installed -eq 0 ] || [ ! $server_up -eq 0 ]; then +use_nailgun=0 +if [ $nailgun_installed -eq 1 ] || [ "$1" = "publishSigned" ] || [ "$2" = "publishSigned" ] || [ "$1" = "direct" ] || [ "$2" = "direct" ]; then + use_nailgun=1 +fi + +if [ $use_nailgun -eq 0 ] && [ ! $server_up -eq 0 ]; then log "Starting up nailgun server." $* # try to start nailgun-server, just in case it's not up - ng-server 127.0.0.1:$NAILGUN_PORT >> $NAILGUN/target/nailgun.stdout.log 2>> $NAILGUN/target/nailgun.stderr.log & + ng-server 127.0.0.1:$NAILGUN_PORT >> $nailgun_out 2>> $nailgun_err & fi log "Downloading Scala jars if necessary..." $* @@ -135,12 +160,12 @@ stage1 () { echo "Compiling cbt/nailgun_launcher" 1>&2 javac -Xlint:deprecation -d $NAILGUN$TARGET `ls $NAILGUN*.java` compiles=$? - if [ $compiles -ne 0 ]; then exit 1; fi - if [ $nailgun_installed -eq 0 ]; then + if [ $compiles -ne 0 ]; then break; fi + if [ $use_nailgun -eq 0 ]; then echo "Stopping nailgun" 1>&2 - $NG ng-stop >> $NAILGUN/target/nailgun.stdout.log 2>> $NAILGUN/target/nailgun.stderr.log & + $NG ng-stop >> $nailgun_out 2>> $nailgun_err & echo "Restarting nailgun" 1>&2 - ng-server 127.0.0.1:$NAILGUN_PORT >> $NAILGUN/target/nailgun.stdout.log 2>> $NAILGUN/target/nailgun.stderr.log & + ng-server 127.0.0.1:$NAILGUN_PORT >> $nailgun_out 2>> $nailgun_err & fi fi @@ -157,7 +182,7 @@ stage1 () { rm $STAGE1$TARGET/cbt/*.class 2>/dev/null $SCALAC -cp $NAILGUN$TARGET -d $STAGE1$TARGET `ls $STAGE1/*.scala` compiles2=$? - if [ $compiles2 -ne 0 ]; then exit 1; fi + if [ $compiles2 -ne 0 ]; then break; fi fi log "run CBT and loop if desired. This allows recompiling CBT itself as part of compile looping." $* @@ -168,27 +193,29 @@ stage1 () { fi CP=$STAGE1$TARGET:$SCALA_CLASSPATH - if [ $nailgun_installed -eq 1 ] || [ "$1" = "publishSigned" ] || [ "$2" = "publishSigned" ] || [ "$1" = "direct" ] || [ "$2" = "direct" ] + if [ $use_nailgun -eq 1 ] then log "Running JVM directly" $* # -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=localhost:5005 java -cp $NAILGUN$TARGET cbt.NailgunLauncher $mainClass $CP "$CWD" $* else log "Running via nailgun." $* - while true; do + for i in 0 1 2 3 4 5 6 7 8 9; do log "Adding classpath." $* - $NG ng-cp $NAILGUN$TARGET >> $NAILGUN/target/nailgun.stdout.log 2>> $NAILGUN/target/nailgun.stderr.log + $NG ng-cp $NAILGUN$TARGET >> $nailgun_out 2>> $nailgun_err log "Checking if nailgun is up yet." $* - $NG cbt.NailgunLauncher cbt.CheckAlive $CP "$CWD" $* >> $NAILGUN/target/nailgun.stdout.log 2>> $NAILGUN/target/nailgun.stderr.log + $NG cbt.NailgunLauncher cbt.CheckAlive $CP "$CWD" $* >> $nailgun_out 2>> $nailgun_err alive=$? - if [[ $alive -eq 131 ]]; then + if [ $alive -eq 131 ]; then echo "Nailgun call failed. Try 'cbt kill' and check the error log cbt/nailgun_launcher/target/nailgun.stderr.log" 1>&2 - elif [[ $alive -eq 33 ]]; then + elif [ $alive -eq 33 ]; then break else - log "Nope. Sleeping for 1 second" $* - echo "Waiting for nailgun to start..." 1>&2 - sleep 1 + log "Nope. Sleeping for 0.5 seconds" $* + if [ "$i" -gt "1" ]; then + echo "Waiting for nailgun to start... (For problems try -Dlog=nailgun or check logs in cbt/nailgun_launcher/target/*.log)" 1>&2 + fi + sleep 0.5 fi done log "Running $mainClass via Nailgun." $* -- cgit v1.2.3 From 4dae0e69f4b2947942d5ff7d4edee482073ee26b Mon Sep 17 00:00:00 2001 From: Christopher Vogt Date: Sun, 6 Mar 2016 23:20:04 -0500 Subject: hack to retrigger a previously failed compilation to happen again for nailgun launcher and stage1. (zinc is still wasting time trying every single time.) --- cbt | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) (limited to 'cbt') diff --git a/cbt b/cbt index d4d51f3..7e721c6 100755 --- a/cbt +++ b/cbt @@ -156,11 +156,14 @@ stage1 () { done compiles=0 if [ $changed -eq 1 ]; then - #rm $NAILGUN$TARGET/cbt/*.class 2>/dev/null + rm $NAILGUN$TARGET/cbt/*.class 2>/dev/null # defensive delete of potentially broken class files echo "Compiling cbt/nailgun_launcher" 1>&2 javac -Xlint:deprecation -d $NAILGUN$TARGET `ls $NAILGUN*.java` compiles=$? - if [ $compiles -ne 0 ]; then break; fi + if [ $compiles -ne 0 ]; then + rm $NAILGUN$TARGET/cbt/*.class 2>/dev/null # triggers recompilation next time. + break + fi if [ $use_nailgun -eq 0 ]; then echo "Stopping nailgun" 1>&2 $NG ng-stop >> $nailgun_out 2>> $nailgun_err & @@ -178,11 +181,14 @@ stage1 () { compiles2=0 if [ $changed2 -eq 1 ]; then + rm $STAGE1$TARGET/cbt/*.class 2>/dev/null # defensive delete of potentially broken class files echo "Compiling cbt/stage1" 1>&2 - rm $STAGE1$TARGET/cbt/*.class 2>/dev/null $SCALAC -cp $NAILGUN$TARGET -d $STAGE1$TARGET `ls $STAGE1/*.scala` compiles2=$? - if [ $compiles2 -ne 0 ]; then break; fi + if [ $compiles2 -ne 0 ]; then + rm $STAGE1$TARGET/cbt/*.class 2>/dev/null # triggers recompilation next time. + break + fi fi log "run CBT and loop if desired. This allows recompiling CBT itself as part of compile looping." $* -- cgit v1.2.3 From f0dc760df8757caea1d83b15142a3d0704488636 Mon Sep 17 00:00:00 2001 From: Christopher Vogt Date: Sun, 6 Mar 2016 23:35:57 -0500 Subject: trap and pass exit codes throug the app, pass logger on to tests, remove the lib. qualification from Stage1 for better readability --- cbt | 6 +++ stage1/Stage1.scala | 26 ++++++------ stage1/Stage1Lib.scala | 106 ++++++++++++++++++++++++++++++++++++---------- stage1/logger.scala | 2 + stage2/DefaultBuild.scala | 13 +++--- stage2/Lib.scala | 41 ++++++------------ stage2/Stage2.scala | 43 ++++++++++--------- stage2/mixins.scala | 2 +- test/test.scala | 31 ++++++++------ 9 files changed, 164 insertions(+), 106 deletions(-) (limited to 'cbt') 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) } } -- cgit v1.2.3 From e958dec0dbbcf7f7a28cd21641e76390fb3dba6a Mon Sep 17 00:00:00 2001 From: Christopher Vogt Date: Mon, 7 Mar 2016 01:36:40 -0500 Subject: cleanup: whitespace changes, separated more things into their own files, use ++ for strings everywhere. Added ++ method to File and URL and use it in many places --- TODO.txt | 2 + cbt | 32 ++--- nailgun_launcher/NailgunLauncher.java | 16 +-- stage1/ClassPath.scala | 30 +++++ stage1/Stage1.scala | 4 +- stage1/Stage1Lib.scala | 64 +++------- stage1/cbt.scala | 21 +++ stage1/classloader.scala | 55 ++++---- stage1/logger.scala | 2 +- stage1/paths.scala | 22 ++-- stage1/resolver.scala | 86 +++++++------ stage2/AdminStage2.scala | 2 +- stage2/BasicBuild.scala | 164 ++++++++++++++++++++++++ stage2/BuildBuild.scala | 3 +- stage2/BuildDependency.scala | 36 ++++++ stage2/DefaultBuild.scala | 234 ---------------------------------- stage2/Lib.scala | 74 +++++------ stage2/PackageBuild.scala | 27 ++++ stage2/PublishBuild.scala | 41 ++++++ stage2/Scaffold.scala | 8 +- stage2/Stage2.scala | 4 +- stage2/dependencies.scala | 36 ------ stage2/mixins.scala | 10 +- test/test.scala | 117 ++++++++--------- 24 files changed, 560 insertions(+), 530 deletions(-) create mode 100644 stage1/ClassPath.scala create mode 100644 stage1/cbt.scala create mode 100644 stage2/BasicBuild.scala create mode 100644 stage2/BuildDependency.scala delete mode 100644 stage2/DefaultBuild.scala create mode 100644 stage2/PackageBuild.scala create mode 100644 stage2/PublishBuild.scala delete mode 100644 stage2/dependencies.scala (limited to 'cbt') diff --git a/TODO.txt b/TODO.txt index 7f29fd1..e5c99f1 100644 --- a/TODO.txt +++ b/TODO.txt @@ -7,6 +7,7 @@ TODO: - condition guarding zinc is too eager, needs to invalid - immediate features + - maybe rename context.cwd - add another class that makes all pom required fields abstract - fix main project main method being run during tests - DI lib into depencies @@ -15,6 +16,7 @@ TODO: - investigate and solve multiple compilations of the same SourceDependency Build. Maybe introduce global Build map. - cleanup + - move from java File to nio Path - defs for all tasks and cached where needed - unify work classpath - unify argument order diff --git a/cbt b/cbt index d9f1ac7..e3c7f1c 100755 --- a/cbt +++ b/cbt @@ -13,11 +13,11 @@ log () { msg=$1 enabled=1 while test $# -gt 0; do - case "$1" in - "-Dlog=time") enabled=0 ;; - "-Dlog=all") enabled=0 ;; - esac - shift + case "$1" in + "-Dlog=time") enabled=0 ;; + "-Dlog=all") enabled=0 ;; + esac + shift done if [ $enabled -eq 0 ]; then which gdate 2>&1 > /dev/null @@ -89,17 +89,17 @@ nailgun_out=$NAILGUN/target/nailgun.stdout.log nailgun_err=$NAILGUN/target/nailgun.strerr.log foo(){ while test $# -gt 0; do - case "$1" in - "-Dlog=nailgun") + case "$1" in + "-Dlog=nailgun") nailgun_out=/dev/stderr nailgun_err=/dev/stderr - ;; - "-Dlog=all") + ;; + "-Dlog=all") nailgun_out=/dev/stderr nailgun_err=/dev/stderr - ;; - esac - shift + ;; + esac + shift done } @@ -147,11 +147,11 @@ SCALAC="java -Xmx256M -Xms32M\ -deprecation\ -feature" -stage1 () { +stage1 () { log "Checking for changes in cbt/nailgun_launcher" $* NAILGUN_INDICATOR=$NAILGUN$TARGET/cbt/NailgunLauncher.class changed=0 - for file in `ls $NAILGUN/*.java`; do + for file in `ls $NAILGUN/*.java`; do if [ $file -nt $NAILGUN_INDICATOR ]; then changed=1; fi done compiles=0 @@ -175,7 +175,7 @@ stage1 () { log "Checking for changes in cbt/stage1" $* STAGE1_INDICATOR=$STAGE1$TARGET/cbt/Stage1.class changed2=0 - for file in `ls $STAGE1*.scala`; do + for file in `ls $STAGE1*.scala`; do if [ $file -nt $STAGE1_INDICATOR ]; then changed2=1; fi done compiles2=0 @@ -229,7 +229,7 @@ stage1 () { fi exitCode=$? log "Done running $mainClass." $* -} +} while true; do stage1 $* diff --git a/nailgun_launcher/NailgunLauncher.java b/nailgun_launcher/NailgunLauncher.java index 3765457..78da041 100644 --- a/nailgun_launcher/NailgunLauncher.java +++ b/nailgun_launcher/NailgunLauncher.java @@ -7,7 +7,7 @@ import java.nio.file.*; import java.util.*; import java.util.concurrent.ConcurrentHashMap; -/** +/** * This launcher allows to start the JVM without loading anything else permanently into its * classpath except for the launcher itself. That's why it is written in Java without * dependencies outside the JDK. @@ -17,16 +17,16 @@ import java.util.concurrent.ConcurrentHashMap; */ public class NailgunLauncher{ - /** + /** * Persistent cache for caching classloaders for the JVM life time. Can be used as needed by user - * code to improve startup time. + * code to improve startup time. */ - public static ConcurrentHashMap classLoaderCache = + public static ConcurrentHashMap classLoaderCache = new ConcurrentHashMap(); - public static void main(String[] args) throws ClassNotFoundException, - NoSuchMethodException, - IllegalAccessException, + public static void main(String[] args) throws ClassNotFoundException, + NoSuchMethodException, + IllegalAccessException, InvocationTargetException, MalformedURLException { if (args.length < 3) { @@ -41,7 +41,7 @@ public class NailgunLauncher{ } String[] newArgs = new String[args.length-2]; - for(int i = 0; i < args.length-2; i++){ + for(int i = 0; i < args.length-2; i++){ newArgs[i] = args[i+2]; } diff --git a/stage1/ClassPath.scala b/stage1/ClassPath.scala new file mode 100644 index 0000000..66a1b44 --- /dev/null +++ b/stage1/ClassPath.scala @@ -0,0 +1,30 @@ +package cbt +import java.io._ +import java.net._ +import scala.collection.immutable.Seq + +object ClassPath{ + def apply(files: File*): ClassPath = ClassPath(files.toVector) + def flatten( classPaths: Seq[ClassPath] ): ClassPath = ClassPath( classPaths.map(_.files).flatten ) +} +case class ClassPath(files: Seq[File]){ + private val duplicates = (files diff files.distinct).distinct + assert( + duplicates.isEmpty, + "Duplicate classpath entries found:\n" ++ duplicates.mkString("\n") ++ "\nin classpath:\n"++string + ) + private val nonExisting = files.distinct.filterNot(_.exists) + assert( + duplicates.isEmpty, + "Classpath contains entires that don't exist on disk:\n" ++ nonExisting.mkString("\n") ++ "\nin classpath:\n"++string + ) + + def +:(file: File) = ClassPath(file +: files) + def :+(file: File) = ClassPath(files :+ file) + def ++(other: ClassPath) = ClassPath(files ++ other.files) + def string = strings.mkString( File.pathSeparator ) + def strings = files.map{ + f => f.string ++ ( if(f.isDirectory) "/" else "" ) + } + def toConsole = string +} diff --git a/stage1/Stage1.scala b/stage1/Stage1.scala index 8db12cf..1aa3f09 100644 --- a/stage1/Stage1.scala +++ b/stage1/Stage1.scala @@ -63,12 +63,12 @@ abstract class Stage1Base{ val cwd = args(0) val src = stage2.listFiles.toVector.filter(_.isFile).filter(_.toString.endsWith(".scala")) - val changeIndicator = new File(stage2Target+"/cbt/Build.class") + val changeIndicator = stage2Target ++ "/cbt/Build.class" logger.stage1("before conditionally running zinc to recompile CBT") if( src.exists(newerThan(_, changeIndicator)) ) { val stage1Classpath = CbtDependency(logger).dependencyClasspath - logger.stage1("cbt.lib has changed. Recompiling with cp: "+stage1Classpath) + logger.stage1("cbt.lib has changed. Recompiling with cp: " ++ stage1Classpath.string) zinc( true, src, stage2Target, stage1Classpath )( zincVersion = "0.3.9", scalaVersion = constants.scalaVersion ) } logger.stage1(s"[$now] calling CbtDependency.classLoader") diff --git a/stage1/Stage1Lib.scala b/stage1/Stage1Lib.scala index ac77a92..1ad3030 100644 --- a/stage1/Stage1Lib.scala +++ b/stage1/Stage1Lib.scala @@ -30,35 +30,13 @@ object TrappedExitCode{ } } +case class Context( cwd: File, args: Seq[String], logger: Logger ) -case class Context( cwd: String, args: Seq[String], logger: Logger ) - -case class ClassPath(files: Seq[File]){ - private val duplicates = (files diff files.distinct).distinct - assert( - duplicates.isEmpty, - "Duplicate classpath entries found:\n" + duplicates.mkString("\n") + "\nin classpath:\n"+string - ) - private val nonExisting = files.distinct.filterNot(_.exists) - assert( - duplicates.isEmpty, - "Classpath contains entires that don't exist on disk:\n" + nonExisting.mkString("\n") + "\nin classpath:\n"+string - ) - - def +:(file: File) = ClassPath(file +: files) - def :+(file: File) = ClassPath(files :+ file) - def ++(other: ClassPath) = ClassPath(files ++ other.files) - def string = strings.mkString( File.pathSeparator ) - def strings = files.map{ - f => f.toString + ( if(f.isDirectory) "/" else "" ) - } - def toConsole = string -} -object ClassPath{ - def flatten( classPaths: Seq[ClassPath] ): ClassPath = ClassPath( classPaths.map(_.files).flatten ) +class BaseLib{ + def realpath(name: File) = new File(Paths.get(name.getAbsolutePath).normalize.toString) } -class Stage1Lib( val logger: Logger ){ +class Stage1Lib( val logger: Logger ) extends BaseLib{ lib => // ========== reflection ========== @@ -76,24 +54,24 @@ class Stage1Lib( val logger: Logger ){ } // ========== file system / net ========== - + def array2hex(padTo: Int, array: Array[Byte]): String = { val hex = new java.math.BigInteger(1, array).toString(16) - ("0" * (padTo-hex.size)) + hex + ("0" * (padTo-hex.size)) ++ hex } def md5( bytes: Array[Byte] ): String = array2hex(32, MessageDigest.getInstance("MD5").digest(bytes)) def sha1( bytes: Array[Byte] ): String = array2hex(40, MessageDigest.getInstance("SHA-1").digest(bytes)) - def red(string: String) = scala.Console.RED+string+scala.Console.RESET - def blue(string: String) = scala.Console.BLUE+string+scala.Console.RESET - def green(string: String) = scala.Console.GREEN+string+scala.Console.RESET + def red(string: String) = scala.Console.RED++string++scala.Console.RESET + def blue(string: String) = scala.Console.BLUE++string++scala.Console.RESET + def green(string: String) = scala.Console.GREEN++string++scala.Console.RESET - def download(urlString: URL, target: Path, sha1: Option[String]){ - val incomplete = Paths.get(target+".incomplete"); - if( !Files.exists(target) ){ - new File(target.toString).getParentFile.mkdirs - logger.resolver(blue("downloading ")+urlString) - logger.resolver(blue("to ")+target) + def download(urlString: URL, target: File, sha1: Option[String]){ + val incomplete = Paths.get( target.string ++ ".incomplete" ); + if( !target.exists ){ + target.getParentFile.mkdirs + logger.resolver(blue("downloading ") ++ urlString.string) + logger.resolver(blue("to ") ++ target.string) val stream = urlString.openStream Files.copy(stream, incomplete, StandardCopyOption.REPLACE_EXISTING) sha1.foreach{ @@ -101,10 +79,10 @@ class Stage1Lib( val logger: Logger ){ val expected = hash val actual = this.sha1(Files.readAllBytes(incomplete)) assert( expected == actual, s"$expected == $actual" ) - logger.resolver(green("verified")+" checksum for "+target) + logger.resolver( green("verified") ++ " checksum for " ++ target.string) } stream.close - Files.move(incomplete, target, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE); + Files.move(incomplete, Paths.get(target.string), StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE); } } @@ -123,7 +101,7 @@ class Stage1Lib( val logger: Logger ){ } def runMain(cls: String, args: Seq[String], classLoader: ClassLoader ): ExitCode = { - logger.lib(s"Running $cls.main($args) with classLoader: "+classLoader) + logger.lib(s"Running $cls.main($args) with classLoader: " ++ classLoader.toString) trapExitCode{ classLoader .loadClass(cls) @@ -148,8 +126,8 @@ class Stage1Lib( val logger: Logger ){ )( zincVersion: String, scalaVersion: String ): Unit = { val cp = classpath.string - if(classpath.files.isEmpty) throw new Exception("Trying to compile with empty classpath. Source files: "+files) - if(files.isEmpty) throw new Exception("Trying to compile no files. ClassPath: "+cp) + if(classpath.files.isEmpty) throw new Exception("Trying to compile with empty classpath. Source files: " ++ files.toString) + if(files.isEmpty) throw new Exception("Trying to compile no files. ClassPath: " ++ cp) // only run zinc if files changed, for performance reasons // FIXME: this is broken, need invalidate on changes in dependencies as well @@ -187,7 +165,7 @@ class Stage1Lib( val logger: Logger ){ "-scala-extra", scalaReflect.toString, "-cp", cp, "-d", compileTarget.toString - ) ++ extraArgs.map("-S"+_) ++ files.map(_.toString), + ) ++ extraArgs.map("-S"++_) ++ files.map(_.toString), zinc.classLoader ) } diff --git a/stage1/cbt.scala b/stage1/cbt.scala new file mode 100644 index 0000000..01af0d5 --- /dev/null +++ b/stage1/cbt.scala @@ -0,0 +1,21 @@ +package cbt +import java.io._ +import java.nio.file._ +import java.net._ +object `package`{ + private val lib = new BaseLib + implicit class FileExtensionMethods( file: File ){ + def ++( s: String ): File = { + if(s endsWith "/") throw new Exception( + """Trying to append a String that ends in "/" to a File would loose it. Use .stripSuffix("/") if you need to.""" + ) + new File( file.toString ++ s ) + } + def parent = lib.realpath(file ++ "/..") + def string = file.toString + } + implicit class URLExtensionMethods( url: URL ){ + def ++( s: String ): URL = new URL( url.toString ++ s ) + def string = url.toString + } +} diff --git a/stage1/classloader.scala b/stage1/classloader.scala index 6f2213b..3293dd1 100644 --- a/stage1/classloader.scala +++ b/stage1/classloader.scala @@ -8,25 +8,29 @@ import scala.collection.immutable.Seq object ClassLoaderCache{ private val cache = NailgunLauncher.classLoaderCache - def classLoader( path: String, parent: ClassLoader ): ClassLoader = { - def realpath( name: String ) = Paths.get(new File(name).getAbsolutePath).normalize.toString - val normalized = realpath(path) - if( cache.containsKey(normalized) ){ - //println("FOUND: "+normalized) - cache.get(normalized) + def classLoader( classpath: ClassPath, parent: ClassLoader )(implicit logger: Logger): ClassLoader + = cache.synchronized{ + val lib = new Stage1Lib(logger) + val key = classpath.strings.sorted.mkString(":") + if( cache.containsKey(key) ){ + logger.resolver("CACHE HIT: "++key) + cache.get(key) } else { - //println("PUTTING: "+normalized) - //Try(???).recover{ case e=>e.printStackTrace} - val cl = new cbt.URLClassLoader( ClassPath(Seq(new File(normalized))), parent ) - cache.put( normalized, cl ) + logger.resolver("CACHE MISS: "++key) + val cl = new cbt.URLClassLoader( classpath, parent ) + cache.put( key, cl ) cl } } - def remove( path: String ) = cache.remove( path ) + def remove( classpath: ClassPath ) = { + val key = classpath.strings.sorted.mkString(":") + cache.remove( key ) + } } +/* class MultiClassLoader(parents: Seq[ClassLoader]) extends ClassLoader { override def loadClass(name: String) = { - //System.err.println("LOADING CLASS "+name); + //System.err.println("LOADING CLASS "++name); val c = parents.toStream.map{ parent => Try{ @@ -37,29 +41,24 @@ class MultiClassLoader(parents: Seq[ClassLoader]) extends ClassLoader { }.find(_.isDefined).flatten c.getOrElse( ClassLoader.getSystemClassLoader.loadClass(name) ) } - override def toString = "MultiClassLoader(" + parents.mkString(",") + ")" + override def toString = "MultiClassLoader(" ++ parents.mkString(",") ++ ")" } +*/ case class URLClassLoader(classPath: ClassPath, parent: ClassLoader) extends java.net.URLClassLoader( classPath.strings.map( - path => new URL("file:"+path) + path => new URL("file:"++path) ).toArray, parent ){ override def toString = ( - scala.Console.BLUE + "cbt.URLClassLoader" + scala.Console.RESET + "(\n " + getURLs.map(_.toString).sorted.mkString(",\n ") - + (if(getParent() != ClassLoader.getSystemClassLoader()) ",\n" + getParent().toString.split("\n").map(" "+_).mkString("\n") else "") - + "\n)" + scala.Console.BLUE ++ "cbt.URLClassLoader" ++ scala.Console.RESET + ++ "(\n " ++ getURLs.map(_.toString).sorted.mkString(",\n ") + ++ ( + if(getParent() != ClassLoader.getSystemClassLoader()) + ",\n" ++ getParent().toString.split("\n").map(" "++_).mkString("\n") + else "" + ) + ++ "\n)" ) - import scala.language.existentials - /*override def loadClass(name: String): Class[_] = { - //System.err.println("LOADING CLASS "+name+" in "+this); - try{ - super.loadClass(name) - } catch { - case e: ClassNotFoundException => - // FIXME: Shouldn't this happen automatically? - parent.loadClass(name) - } - }*/ } diff --git a/stage1/logger.scala b/stage1/logger.scala index ecc1579..b1d80a3 100644 --- a/stage1/logger.scala +++ b/stage1/logger.scala @@ -19,7 +19,7 @@ case class Logger(enabledLoggers: Set[String]) { System.err.println( s"[${" "*(6-timeTaken.size)}$timeTaken][$name] $msg" ) } - def showInvocation(method: String, args: Any) = method + "( " + args + " )" + def showInvocation(method: String, args: Any) = method ++ "( " ++ args.toString ++ " )" final def stage1(msg: => String) = logGuarded(names.stage1, msg) final def stage2(msg: => String) = logGuarded(names.stage2, msg) diff --git a/stage1/paths.scala b/stage1/paths.scala index f76c2f7..d3856c8 100644 --- a/stage1/paths.scala +++ b/stage1/paths.scala @@ -1,15 +1,15 @@ package cbt import java.io._ object paths{ - val cbtHome = new File(Option(System.getenv("CBT_HOME")).get) - val mavenCache = new File(cbtHome+"/cache/maven/") - val userHome = new File(Option(System.getProperty("user.home")).get) - val stage1 = new File(Option(System.getenv("STAGE1")).get) - val stage2 = new File(cbtHome + "/stage2/") - val nailgun = new File(Option(System.getenv("NAILGUN")).get) - private val target = Option(System.getenv("TARGET")).get - val stage1Target = new File(stage1 + "/" + target) - val stage2Target = new File(stage2 + "/" + target) - val nailgunTarget = new File(nailgun + "/" + target) - val sonatypeLogin = new File(cbtHome+"/sonatype.login") + val cbtHome: File = new File(Option(System.getenv("CBT_HOME")).get) + val mavenCache: File = cbtHome ++ "/cache/maven" + val userHome: File = new File(Option(System.getProperty("user.home")).get) + val stage1: File = new File(Option(System.getenv("STAGE1")).get) + val stage2: File = cbtHome ++ "/stage2" + val nailgun: File = new File(Option(System.getenv("NAILGUN")).get) + private val target = Option(System.getenv("TARGET")).get.stripSuffix("/") + val stage1Target: File = stage1 ++ ("/" ++ target) + val stage2Target: File = stage2 ++ ("/" ++ target) + val nailgunTarget: File = nailgun ++ ("/" ++ target) + val sonatypeLogin: File = cbtHome ++ "/sonatype.login" } diff --git a/stage1/resolver.scala b/stage1/resolver.scala index 880289c..9b3276b 100644 --- a/stage1/resolver.scala +++ b/stage1/resolver.scala @@ -10,7 +10,7 @@ private final class Tree( val root: Dependency, computeChildren: => Seq[Tree] ){ lazy val children = computeChildren def linearize: Seq[Dependency] = root +: children.flatMap(_.linearize) def show(indent: Int = 0): Stream[Char] = { - (" " * indent + root.show + "\n").toStream #::: children.map(_.show(indent+1)).foldLeft(Stream.empty[Char])(_ #::: _) + (" " * indent ++ root.show ++ "\n").toStream #::: children.map(_.show(indent+1)).foldLeft(Stream.empty[Char])(_ #::: _) } } @@ -20,9 +20,11 @@ trait ArtifactInfo extends Dependency{ def version: String protected def str = s"$groupId:$artifactId:$version" - override def show = super.show + s"($str)" + override def show = super.show ++ s"($str)" } abstract class Dependency{ + implicit def logger: Logger + protected def lib = new Stage1Lib(logger) def updated: Boolean //def cacheClassLoader: Boolean = false @@ -49,11 +51,10 @@ abstract class Dependency{ ).par.map(_.exportedClasspath).seq.sortBy(_.string) ) if(cacheDependencyClassLoader){ - val mavenClassPathKey = mavenClassPath.strings.sorted.mkString(":") new URLClassLoader( exportedClasspath ++ buildClassPath, ClassLoaderCache.classLoader( - mavenClassPathKey, new URLClassLoader( mavenClassPath, ClassLoader.getSystemClassLoader ) + mavenClassPath, new URLClassLoader( mavenClassPath, ClassLoader.getSystemClassLoader ) ) ) } else { @@ -85,11 +86,13 @@ abstract class Dependency{ def show: String = this.getClass.getSimpleName // ========== debug ========== def dependencyTree: String = dependencyTreeRecursion() - def logger: Logger - protected def lib = new Stage1Lib(logger) - private def dependencyTreeRecursion(indent: Int = 0): String = ( " " * indent ) + (if(updated) lib.red(show) else show) + dependencies.map(_.dependencyTreeRecursion(indent + 1)).map("\n"+_).mkString("") - - private object cacheDependencyClassLoaderBasicBuild extends Cache[ClassLoader] + private def dependencyTreeRecursion(indent: Int = 0): String = ( + ( " " * indent ) + ++ (if(updated) lib.red(show) else show) + ++ dependencies.map( + _.dependencyTreeRecursion(indent + 1) + ).map( "\n" ++ _.toString ).mkString("") + ) } // TODO: all this hard codes the scala version, needs more flexibility @@ -140,16 +143,18 @@ case class MavenDependency( groupId: String, artifactId: String, version: String def updated = false private val groupPath = groupId.split("\\.").mkString("/") - def basePath = s"/$groupPath/$artifactId/$version/$artifactId-$version"+(if(sources) "-sources" else "") + def basePath = s"/$groupPath/$artifactId/$version/$artifactId-$version"++(if(sources) "-sources" else "") - private def resolverUrl = if(version.endsWith("-SNAPSHOT")) "https://oss.sonatype.org/content/repositories/snapshots" else "https://repo1.maven.org/maven2" - private def baseUrl = resolverUrl + basePath - private def baseFile = mavenCache + basePath - private def pomFile = baseFile+".pom" - private def jarFile = baseFile+".jar" - //private def coursierJarFile = userHome+"/.coursier/cache/v1/https/repo1.maven.org/maven2"+basePath+".jar" - private def pomUrl = baseUrl+".pom" - private def jarUrl = baseUrl+".jar" + private def resolverUrl:URL = new URL( + if(version.endsWith("-SNAPSHOT")) "https://oss.sonatype.org/content/repositories/snapshots" else "https://repo1.maven.org/maven2" + ) + private def baseUrl: URL = resolverUrl ++ basePath + private def baseFile: File = mavenCache ++ basePath + private def pomFile: File = baseFile ++ ".pom" + private def jarFile: File = baseFile ++ ".jar" + //private def coursierJarFile = userHome++"/.coursier/cache/v1/https/repo1.maven.org/maven2"++basePath++".jar" + private def pomUrl: URL = baseUrl ++ ".pom" + private def jarUrl: URL = baseUrl ++ ".jar" def exportedJars = Seq( jar ) def exportedClasspath = ClassPath( exportedJars ) @@ -157,33 +162,32 @@ case class MavenDependency( groupId: String, artifactId: String, version: String import scala.collection.JavaConversions._ def jarSha1 = { - val file = jarFile+".sha1" - def url = jarUrl+".sha1" + val file = jarFile ++ ".sha1" scala.util.Try{ - lib.download( new URL(url), Paths.get(file), None ) + lib.download( jarUrl ++ ".sha1" , file, None ) // split(" ") here so checksum file contents in this format work: df7f15de037a1ee4d57d2ed779739089f560338c jna-3.2.2.pom - Files.readAllLines(Paths.get(file)).mkString("\n").split(" ").head.trim - }.toOption // FIXME: .toOption is a temporary solution to ignore if libs don't have one + Files.readAllLines(Paths.get(file.string)).mkString("\n").split(" ").head.trim + }.toOption // FIXME: .toOption is a temporary solution to ignore if libs don't have one (not sure that's even possible) } + def pomSha1 = { - val file = pomFile+".sha1" - def url = pomUrl+".sha1" + val file = pomFile++".sha1" scala.util.Try{ - lib.download( new URL(url), Paths.get(file), None ) + lib.download( pomUrl++".sha1" , file, None ) // split(" ") here so checksum file contents in this format work: df7f15de037a1ee4d57d2ed779739089f560338c jna-3.2.2.pom - Files.readAllLines(Paths.get(file)).mkString("\n").split(" ").head.trim - }.toOption // FIXME: .toOption is a temporary solution to ignore if libs don't have one + Files.readAllLines(Paths.get(file.string)).mkString("\n").split(" ").head.trim + }.toOption // FIXME: .toOption is a temporary solution to ignore if libs don't have one (not sure that's even possible) } + def jar = { - lib.download( new URL(jarUrl), Paths.get(jarFile), jarSha1 ) - new File(jarFile) - } - def pomXml = { - XML.loadFile(pom.toString) + lib.download( jarUrl, jarFile, jarSha1 ) + jarFile } + def pomXml = XML.loadFile(pom.toString) + def pom = { - lib.download( new URL(pomUrl), Paths.get(pomFile), pomSha1 ) - new File(pomFile) + lib.download( pomUrl, pomFile, pomSha1 ) + pomFile } // ========== pom traversal ========== @@ -211,13 +215,13 @@ case class MavenDependency( groupId: String, artifactId: String, version: String }.toVector } def lookup( xml: Node, accessor: Node => NodeSeq ): Option[String] = { - //println("lookup in "+pomUrl) - val Substitution = "\\$\\{([a-z0-9\\.]+)\\}".r + //println("lookup in "++pomUrl) + val Substitution = "\\$\\{([a-z0-9\\.]++)\\}".r accessor(xml).headOption.flatMap{v => - //println("found: "+v.text) + //println("found: "++v.text) v.text match { case Substitution(path) => - //println("lookup "+path + ": "+(pomXml\path).text) + //println("lookup "++path ++ ": "++(pomXml\path).text) lookup(pomXml, _ \ "properties" \ path) case value => Option(value) } @@ -247,7 +251,7 @@ object MavenDependency{ def removeOutdated( deps: Seq[ArtifactInfo], versionLessThan: (String, String) => Boolean = semanticVersionLessThan - ): Seq[ArtifactInfo] = { + )(implicit logger: Logger): Seq[ArtifactInfo] = { val latest = deps .groupBy( d => (d.groupId, d.artifactId) ) .mapValues( @@ -257,7 +261,7 @@ object MavenDependency{ deps.flatMap{ d => val l = latest.get((d.groupId,d.artifactId)) - //if(d != l) println("EVICTED: "+d.show) + if(d != l) logger.resolver("outdated: "++d.show) l }.distinct } diff --git a/stage2/AdminStage2.scala b/stage2/AdminStage2.scala index d3db3e7..4120b1c 100644 --- a/stage2/AdminStage2.scala +++ b/stage2/AdminStage2.scala @@ -5,7 +5,7 @@ object AdminStage2{ val lib = new Lib(init.logger) val adminTasks = new AdminTasks(lib, args.drop(3)) new lib.ReflectObject(adminTasks){ - def usage = "Available methods: " + lib.taskNames(subclassType) + def usage: String = "Available methods: " ++ lib.taskNames(subclassType).mkString(" ") }.callNullary(args.lift(2)) } } diff --git a/stage2/BasicBuild.scala b/stage2/BasicBuild.scala new file mode 100644 index 0000000..6da31b7 --- /dev/null +++ b/stage2/BasicBuild.scala @@ -0,0 +1,164 @@ +package cbt +import cbt.paths._ + +import java.io._ +import java.lang.reflect.InvocationTargetException +import java.net._ +import java.nio.file.{Path =>_,_} +import java.nio.file.Files.readAllBytes +import java.security.MessageDigest +import java.util.jar._ + +import scala.collection.immutable.Seq +import scala.reflect.runtime.{universe => ru} +import scala.util._ + +import ammonite.ops.{cwd => _,_} + +class BasicBuild(context: Context) extends Build(context) +class Build(val context: Context) extends Dependency with TriggerLoop{ + // library available to builds + final val logger = context.logger + override final protected val lib: Lib = new Lib(logger) + + // ========== general stuff ========== + + def enableConcurrency = false + final def projectDirectory: File = context.cwd + assert( projectDirectory.exists, "projectDirectory does not exist: " ++ projectDirectory.string ) + final def usage: Unit = new lib.ReflectBuild(this).usage +/* + def scaffold: Unit = lib.generateBasicBuildFile( + projectDirectory, scalaVersion, groupId, artifactId, version + ) +*/ + // ========== meta data ========== + + def scalaVersion: String = constants.scalaVersion + final def scalaMajorVersion: String = scalaVersion.split("\\.").take(2).mkString(".") + def zincVersion = "0.3.9" + + def dependencies: Seq[Dependency] = Seq( + "org.scala-lang" % "scala-library" % scalaVersion + ) + + // ========== paths ========== + final private val defaultSourceDirectory = projectDirectory ++ "/src" + + /** base directory where stuff should be generated */ + def target: File = projectDirectory ++ "/target" + /** base directory where stuff should be generated for this scala version*/ + def scalaTarget: File = target ++ s"/scala-$scalaMajorVersion" + /** directory where jars (and the pom file) should be put */ + def jarTarget: File = scalaTarget + /** directory where the scaladoc should be put */ + def apiTarget: File = scalaTarget ++ "/api" + /** directory where the class files should be put (in package directories) */ + def compileTarget: File = scalaTarget ++ "/classes" + + /** 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. */ + 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. */ + final def sourceFiles: Seq[File] = for { + 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 + .filterNot( _.exists ) + .diff( Seq(defaultSourceDirectory) ) + assert( + nonExisting.isEmpty, + "Some sources do not exist: \n"++nonExisting.mkString("\n") + ) + } + assertSourceDirectories() + + + /** SBT-like dependency builder DSL */ + class GroupIdAndArtifactId( groupId: String, artifactId: String ){ + def %(version: String) = new MavenDependency(groupId, artifactId, version)(lib.logger) + } + implicit class DependencyBuilder(groupId: String){ + def %%(artifactId: String) = new GroupIdAndArtifactId( groupId, artifactId++"_"++scalaMajorVersion ) + def %(artifactId: String) = new GroupIdAndArtifactId( groupId, artifactId ) + } + + final def BuildDependency(path: File) = cbt.BuildDependency( + context.copy( cwd = path, args = Seq() ) + ) + + def triggerLoopFiles: Seq[File] = sources ++ transitiveDependencies.collect{ case b: TriggerLoop => b.triggerLoopFiles }.flatten + + def localJars : Seq[File] = + Seq(projectDirectory ++ "/lib") + .filter(_.exists) + .flatMap(_.listFiles) + .filter(_.toString.endsWith(".jar")) + + //def cacheJar = false + override def dependencyClasspath : ClassPath = ClassPath(localJars) ++ super.dependencyClasspath + override def dependencyJars : Seq[File] = localJars ++ super.dependencyJars + + def exportedClasspath : ClassPath = ClassPath(Seq(compile)) + def exportedJars: Seq[File] = Seq() + // ========== compile, run, test ========== + + /** scalac options used for zinc and scaladoc */ + def scalacOptions: Seq[String] = Seq( "-feature", "-deprecation", "-unchecked" ) + + val updated: Boolean = { + val existingClassFiles = lib.listFilesRecursive(compileTarget) + val sourcesChanged = existingClassFiles.nonEmpty && { + val oldestClassFile = existingClassFiles.sortBy(_.lastModified).head + val oldestClassFileAge = oldestClassFile.lastModified + val changedSourceFiles = sourceFiles.filter(_.lastModified > oldestClassFileAge) + if(changedSourceFiles.nonEmpty){ + /* + println(changedSourceFiles) + println(changedSourceFiles.map(_.lastModified)) + println(changedSourceFiles.map(_.lastModified > oldestClassFileAge)) + println(oldestClassFile) + println(oldestClassFileAge) + println("-"*80) + */ + } + changedSourceFiles.nonEmpty + } + sourcesChanged || transitiveDependencies.map(_.updated).fold(false)(_ || _) + } + + private object cacheCompileBasicBuild extends Cache[File] + def compile: File = cacheCompileBasicBuild{ + //println(transitiveDependencies.filter(_.updated).mkString("\n")) + lib.compile( + updated, + sourceFiles, compileTarget, dependencyClasspath, scalacOptions, + zincVersion = zincVersion, scalaVersion = scalaVersion + ) + } + + def runClass: String = "Main" + def run: ExitCode = lib.runMainIfFound( runClass, context.args, classLoader ) + + def test: ExitCode = lib.test(context) + + context.logger.composition(">"*80) + context.logger.composition("class " ++ this.getClass.toString) + context.logger.composition("dir " ++ context.cwd.string) + context.logger.composition("sources " ++ sources.toList.mkString(" ")) + context.logger.composition("target " ++ target.string) + context.logger.composition("context " ++ context.toString) + context.logger.composition("dependencyTree\n" ++ dependencyTree) + context.logger.composition("<"*80) + + // ========== cbt internals ========== + private[cbt] def finalBuild = this + override def show = this.getClass.getSimpleName ++ "(" ++ context.cwd.string ++ ")" +} diff --git a/stage2/BuildBuild.scala b/stage2/BuildBuild.scala index 41589db..9283cdf 100644 --- a/stage2/BuildBuild.scala +++ b/stage2/BuildBuild.scala @@ -1,9 +1,10 @@ package cbt +import java.io.File import scala.collection.immutable.Seq class BuildBuild(context: Context) extends Build(context){ override def dependencies = Seq( CbtDependency(context.logger) ) ++ super.dependencies - def managedBuildDirectory = lib.realpath(projectDirectory + "/../") + def managedBuildDirectory: File = lib.realpath( projectDirectory.parent ) val managedBuild = { val managedContext = context.copy( cwd = managedBuildDirectory ) val cl = new cbt.URLClassLoader( diff --git a/stage2/BuildDependency.scala b/stage2/BuildDependency.scala new file mode 100644 index 0000000..f7b6b78 --- /dev/null +++ b/stage2/BuildDependency.scala @@ -0,0 +1,36 @@ +package cbt +import java.io.File +import scala.collection.immutable.Seq +/* +sealed abstract class ProjectProxy extends Ha{ + protected def delegate: ProjectMetaData + def artifactId: String = delegate.artifactId + def groupId: String = delegate.groupId + def version: String = delegate.version + def exportedClasspath = delegate.exportedClasspath + def dependencies = Seq(delegate) +} +*/ +trait TriggerLoop extends Dependency{ + def triggerLoopFiles: Seq[File] +} +/** 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 ++ ")" + 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()) ) + lazy val build = root.finalBuild + def exportedClasspath = ClassPath(Seq()) + def exportedJars = Seq() + def dependencies = Seq(build) + def triggerLoopFiles = root.triggerLoopFiles + final val updated = build.updated +} +/* +case class DependencyOr(first: BuildDependency, second: MavenDependency) extends ProjectProxy with BuildDependencyBase{ + val isFirst = new File(first.context.cwd).exists + def triggerLoopFiles = if(isFirst) first.triggerLoopFiles else Seq() + protected val delegate = if(isFirst) first else second +} +*/ \ No newline at end of file diff --git a/stage2/DefaultBuild.scala b/stage2/DefaultBuild.scala deleted file mode 100644 index c98aea0..0000000 --- a/stage2/DefaultBuild.scala +++ /dev/null @@ -1,234 +0,0 @@ -package cbt -import cbt.paths._ - -import java.io._ -import java.lang.reflect.InvocationTargetException -import java.net._ -import java.nio.file.{Path =>_,_} -import java.nio.file.Files.readAllBytes -import java.security.MessageDigest -import java.util.jar._ - -import scala.collection.immutable.Seq -import scala.reflect.runtime.{universe => ru} -import scala.util._ - -import ammonite.ops.{cwd => _,_} - - - - -abstract class PackageBuild(context: Context) extends Build(context) with ArtifactInfo{ - def `package`: Seq[File] = lib.concurrently( enableConcurrency )( - Seq(() => jar, () => docJar, () => srcJar) - )( _() ) - - private object cacheJarBasicBuild extends Cache[File] - def jar: File = cacheJarBasicBuild{ - lib.jar( artifactId, version, compile, jarTarget ) - } - - private object cacheSrcJarBasicBuild extends Cache[File] - def srcJar: File = cacheSrcJarBasicBuild{ - lib.srcJar(sources, artifactId, version, scalaTarget) - } - - private object cacheDocBasicBuild extends Cache[File] - def docJar: File = cacheDocBasicBuild{ - lib.docJar( sources, dependencyClasspath, apiTarget, jarTarget, artifactId, version, scalacOptions ) - } - - override def jars = jar +: dependencyJars - override def exportedJars: Seq[File] = Seq(jar) -} -abstract class PublishBuild(context: Context) extends PackageBuild(context){ - def name = artifactId - def description: String - def url: URL - def developers: Seq[Developer] - def licenses: Seq[License] - def scmUrl: String - def scmConnection: String - def pomExtra: Seq[scala.xml.Node] = Seq() - - // ========== package ========== - - /** put additional xml that should go into the POM file in here */ - def pom: File = lib.pom( - groupId = groupId, - artifactId = artifactId, - version = version, - name = name, - description = description, - url = url, - developers = developers, - licenses = licenses, - scmUrl = scmUrl, - scmConnection = scmConnection, - dependencies = dependencies, - pomExtra = pomExtra, - jarTarget = jarTarget - ) - - // ========== publish ========== - final protected def releaseFolder = s"/${groupId.replace(".","/")}/$artifactId/$version/" - def snapshotUrl = new URL("https://oss.sonatype.org/content/repositories/snapshots") - def releaseUrl = new URL("https://oss.sonatype.org/service/local/staging/deploy/maven2") - def publishSnapshot: Unit = lib.publishSnapshot(sourceFiles, pom +: `package`, new URL(snapshotUrl + releaseFolder) ) - def publishSigned: Unit = lib.publishSigned(sourceFiles, pom +: `package`, new URL(releaseUrl + releaseFolder) ) -} - - -class BasicBuild(context: Context) extends Build(context) -class Build(val context: Context) extends Dependency with TriggerLoop{ - // library available to builds - 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 ) - final def usage: Unit = new lib.ReflectBuild(this).usage -/* - def scaffold: Unit = lib.generateBasicBuildFile( - projectDirectory, scalaVersion, groupId, artifactId, version - ) -*/ - // ========== meta data ========== - - def scalaVersion: String = constants.scalaVersion - final def scalaMajorVersion: String = scalaVersion.split("\\.").take(2).mkString(".") - def zincVersion = "0.3.9" - - def dependencies: Seq[Dependency] = Seq( - "org.scala-lang" % "scala-library" % scalaVersion - ) - - // ========== paths ========== - final private val defaultSourceDirectory = new File(projectDirectory+"/src/") - - /** base directory where stuff should be generated */ - def target = new File(projectDirectory+"/target") - /** base directory where stuff should be generated for this scala version*/ - def scalaTarget = new File(target + s"/scala-$scalaMajorVersion") - /** directory where jars (and the pom file) should be put */ - def jarTarget = scalaTarget - /** directory where the scaladoc should be put */ - def apiTarget = new File(scalaTarget + "/api") - /** directory where the class files should be put (in package directories) */ - def compileTarget = new File(scalaTarget + "/classes") - - /** 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. */ - 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. */ - final def sourceFiles: Seq[File] = for { - 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 - .filterNot( _.exists ) - .diff( Seq(defaultSourceDirectory) ) - assert( - nonExisting.isEmpty, - "Some sources do not exist: \n"+nonExisting.mkString("\n") - ) - } - assertSourceDirectories() - - - - - /** SBT-like dependency builder DSL */ - class GroupIdAndArtifactId( groupId: String, artifactId: String ){ - def %(version: String) = new MavenDependency(groupId, artifactId, version)(lib.logger) - } - implicit class DependencyBuilder(groupId: String){ - def %%(artifactId: String) = new GroupIdAndArtifactId( groupId, artifactId+"_"+scalaMajorVersion ) - def %(artifactId: String) = new GroupIdAndArtifactId( groupId, artifactId ) - } - - final def BuildDependency(path: String) = cbt.BuildDependency( - context.copy( - cwd = path, - args = Seq() - ) - ) - - def triggerLoopFiles: Seq[File] = sources ++ transitiveDependencies.collect{ case b: TriggerLoop => b.triggerLoopFiles }.flatten - - - def localJars : Seq[File] = - Seq(projectDirectory + "/lib/") - .map(new File(_)) - .filter(_.exists) - .flatMap(_.listFiles) - .filter(_.toString.endsWith(".jar")) - - //def cacheJar = false - override def dependencyClasspath : ClassPath = ClassPath(localJars) ++ super.dependencyClasspath - override def dependencyJars : Seq[File] = localJars ++ super.dependencyJars - - def exportedClasspath : ClassPath = ClassPath(Seq(compile)) - def exportedJars: Seq[File] = Seq() - // ========== compile, run, test ========== - - /** scalac options used for zinc and scaladoc */ - def scalacOptions: Seq[String] = Seq( "-feature", "-deprecation", "-unchecked" ) - - val updated: Boolean = { - val existingClassFiles = lib.listFilesRecursive(compileTarget) - val sourcesChanged = existingClassFiles.nonEmpty && { - val oldestClassFile = existingClassFiles.sortBy(_.lastModified).head - val oldestClassFileAge = oldestClassFile.lastModified - val changedSourceFiles = sourceFiles.filter(_.lastModified > oldestClassFileAge) - if(changedSourceFiles.nonEmpty){ - /* - println(changedSourceFiles) - println(changedSourceFiles.map(_.lastModified)) - println(changedSourceFiles.map(_.lastModified > oldestClassFileAge)) - println(oldestClassFile) - println(oldestClassFileAge) - println("-"*80) - */ - } - changedSourceFiles.nonEmpty - } - sourcesChanged || transitiveDependencies.map(_.updated).fold(false)(_ || _) - } - - private object cacheCompileBasicBuild extends Cache[File] - def compile: File = cacheCompileBasicBuild{ - //println(transitiveDependencies.filter(_.updated).mkString("\n")) - lib.compile( - updated, - sourceFiles, compileTarget, dependencyClasspath, scalacOptions, - zincVersion = zincVersion, scalaVersion = scalaVersion - ) - } - - def runClass: String = "Main" - def run: ExitCode = lib.runMainIfFound( runClass, context.args, classLoader ) - - 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) - - // ========== cbt internals ========== - private[cbt] def finalBuild = this - override def show = this.getClass.getSimpleName + "("+context.cwd+")" -} diff --git a/stage2/Lib.scala b/stage2/Lib.scala index 9971b55..6bb9c0b 100644 --- a/stage2/Lib.scala +++ b/stage2/Lib.scala @@ -37,9 +37,9 @@ final class Lib(logger: Logger) extends Stage1Lib(logger) with Scaffold{ */ def loadRoot(context: Context, default: Context => Build = new Build(_)): Build = { context.logger.composition( context.logger.showInvocation("Build.loadRoot",context) ) - def findStartDir(cwd: String): String = { - val buildDir = realpath(cwd+"/build") - if(new File(buildDir).exists) findStartDir(buildDir) else cwd + def findStartDir(cwd: File): File = { + val buildDir = realpath( cwd ++ "/build" ) + if(buildDir.exists) findStartDir(buildDir) else cwd } val start = findStartDir(context.cwd) @@ -51,7 +51,7 @@ final class Lib(logger: Logger) extends Stage1Lib(logger) with Scaffold{ if(useBasicBuildBuild) default( context ) else new cbt.BuildBuild( context.copy( cwd = start ) ) } catch { case e:ClassNotFoundException if e.getMessage == rootBuildClassName => - throw new Exception(s"no class $rootBuildClassName found in "+start) + throw new Exception(s"no class $rootBuildClassName found in " ++ start.string) } } @@ -68,13 +68,13 @@ final class Lib(logger: Logger) extends Stage1Lib(logger) with Scaffold{ } def srcJar(sources: Seq[File], artifactId: String, version: String, jarTarget: File): File = { - val file = new File(jarTarget+"/"+artifactId+"-"+version+"-sources.jar") + val file = jarTarget ++ ("/"++artifactId++"-"++version++"-sources.jar") lib.jarFile(file, sources) file } def jar(artifactId: String, version: String, compileTarget: File, jarTarget: File): File = { - val file = new File(jarTarget+"/"+artifactId+"-"+version+".jar") + val file = jarTarget ++ ("/"++artifactId++"-"++version++".jar") lib.jarFile(file, Seq(compileTarget)) file } @@ -102,7 +102,7 @@ final class Lib(logger: Logger) extends Stage1Lib(logger) with Scaffold{ "scala.tools.nsc.ScalaDoc", Seq( // FIXME: can we use compiler dependency here? - "-cp", /*javacp+":"+*/ScalaDependencies(logger).classpath.string + ":" + dependenyClasspath.string, + "-cp", /*javacp++":"++*/ScalaDependencies(logger).classpath.string ++ ":" ++ dependenyClasspath.string, "-d", apiTarget.toString ) ++ compileArgs ++ sourceFiles.map(_.toString), new URLClassLoader( @@ -113,7 +113,7 @@ final class Lib(logger: Logger) extends Stage1Lib(logger) with Scaffold{ } } } - val docJar = new File(jarTarget+"/"+artifactId+"-"+version+"-javadoc.jar") + val docJar = jarTarget ++ ("/"++artifactId++"-"++version++"-javadoc.jar") lib.jarFile(docJar, Vector(apiTarget)) docJar } @@ -122,11 +122,11 @@ final class Lib(logger: Logger) extends Stage1Lib(logger) with Scaffold{ 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 + val loggerArg = if(loggers != "") Some("-Dlog="++loggers) else None logger.lib(s"invoke testDefault( $context )") val exitCode: ExitCode = loadDynamic( - context.copy( cwd = context.cwd+"/test/", args = loggerArg.toVector ++ context.args ), + context.copy( cwd = context.cwd ++ "/test", args = loggerArg.toVector ++ context.args ), new Build(_) with mixins.Test ).run logger.lib(s"return testDefault( $context )") @@ -135,9 +135,9 @@ final class Lib(logger: Logger) extends Stage1Lib(logger) with Scaffold{ // task reflection helpers import ru._ - private lazy val anyRefMembers = ru.typeOf[AnyRef].members.toVector.map(taskName) - def taskNames(tpe: Type) = tpe.members.toVector.flatMap(lib.toTask).map(taskName).sorted - private def taskName(method: Symbol) = method.name.decodedName.toString + private lazy val anyRefMembers: Set[String] = ru.typeOf[AnyRef].members.toSet.map(taskName) + def taskNames(tpe: Type): Seq[String] = tpe.members.toVector.flatMap(lib.toTask).map(taskName).sorted + private def taskName(method: Symbol): String = method.name.decodedName.toString def toTask(symbol: Symbol): Option[MethodSymbol] = { Option(symbol) .filter(_.isPublic) @@ -161,10 +161,10 @@ final class Lib(logger: Logger) extends Stage1Lib(logger) with Scaffold{ """ } else "" - ) + s"""Methods provided by CBT (but possibly overwritten) + ) ++ s"""Methods provided by CBT (but possibly overwritten) ${baseTasks.mkString(" ")}""" - ) + "\n" + ) ++ "\n" } } @@ -202,17 +202,13 @@ final class Lib(logger: Logger) extends Stage1Lib(logger) with Scaffold{ } } - // file system helpers - def basename(path: String) = path.stripSuffix("/").split("/").last - def basename(path: File) = path.toString.stripSuffix("/").split("/").last - def dirname(path: String) = realpath(path).stripSuffix("/").split("/").dropRight(1).mkString("/") - def realpath(name: String) = Paths.get(new File(name).getAbsolutePath).normalize.toString - def realpath(name: File) = new File(Paths.get(name.getAbsolutePath).normalize.toString) - def nameAndContents(file: File) = basename(file.toString) -> readAllBytes(Paths.get(file.toString)) + def basename(path: File): String = path.toString.stripSuffix("/").split("/").last + def dirname(path: File): File = new File(realpath(path).string.stripSuffix("/").split("/").dropRight(1).mkString("/")) + def nameAndContents(file: File) = basename(file) -> readAllBytes(Paths.get(file.toString)) def jarFile( jarFile: File, files: Seq[File] ): Unit = { - logger.lib("Start packaging "+jarFile) + logger.lib("Start packaging "++jarFile.string) val manifest = new Manifest manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0") val jar = new JarOutputStream(new FileOutputStream(jarFile.toString), manifest) @@ -227,7 +223,7 @@ final class Lib(logger: Logger) extends Stage1Lib(logger) with Scaffold{ val entry = new JarEntry( name ) entry.setTime(file.lastModified) jar.putNextEntry(entry) - jar.write( Files.readAllBytes( Paths.get(file.toString) ) ) + jar.write( readAllBytes( Paths.get(file.toString) ) ) jar.closeEntry name } @@ -235,11 +231,11 @@ final class Lib(logger: Logger) extends Stage1Lib(logger) with Scaffold{ val duplicateFiles = (names diff names.distinct).distinct assert( duplicateFiles.isEmpty, - s"Conflicting file names when trying to create $jarFile: "+duplicateFiles.mkString(", ") + s"Conflicting file names when trying to create $jarFile: "++duplicateFiles.mkString(", ") ) jar.close - logger.lib("Done packaging "+jarFile) + logger.lib("Done packaging " ++ jarFile.toString) } lazy val passphrase = @@ -255,9 +251,9 @@ final class Lib(logger: Logger) extends Stage1Lib(logger) with Scaffold{ new ProcessBuilder( "gpg", "--batch", "--yes", "-a", "-b", "-s", "--passphrase", passphrase, file.toString ) .inheritIO.start.waitFor - if( 0 != statusCode ) throw new Exception("gpg exited with status code "+statusCode) + if( 0 != statusCode ) throw new Exception("gpg exited with status code " ++ statusCode.toString) - new File(file+".asc") + file ++ ".asc" } //def requiredForPom[T](name: String): T = throw new Exception(s"You need to override `def $name` in order to generate a valid pom.") @@ -324,9 +320,9 @@ final class Lib(logger: Logger) extends Stage1Lib(logger) with Scaffold{ } - val path = new File(jarTarget+"/"+artifactId+"-"+version+".pom") - write.over(Path(path), "\n" + xml.toString) - path + val path = jarTarget.toString ++ ( "/" ++ artifactId ++ "-" ++ version ++ ".pom" ) + write.over(Path(path), "\n" ++ xml.toString) + new File(path) } def concurrently[T,R]( concurrencyEnabled: Boolean )( items: Seq[T] )( projection: T => R ): Seq[R] = { @@ -347,8 +343,8 @@ final class Lib(logger: Logger) extends Stage1Lib(logger) with Scaffold{ val files = (artifacts ++ artifacts.map(sign)).map(nameAndContents) lazy val checksums = files.flatMap{ case (name, content) => Seq( - name+".md5" -> md5(content).toArray.map(_.toByte), - name+".sha1" -> sha1(content).toArray.map(_.toByte) + name++".md5" -> md5(content).toArray.map(_.toByte), + name++".sha1" -> sha1(content).toArray.map(_.toByte) ) } val all = (files ++ checksums) @@ -363,16 +359,14 @@ final class Lib(logger: Logger) extends Stage1Lib(logger) with Scaffold{ def upload(fileName: String, fileContents: Array[Byte], baseUrl: URL): Unit = { import java.net._ import java.io._ - logger.task("uploading "+fileName) - val url = new URL( - baseUrl + fileName - ) + logger.task("uploading "++fileName) + val url = baseUrl ++ fileName val httpCon = url.openConnection.asInstanceOf[HttpURLConnection] httpCon.setDoOutput(true) httpCon.setRequestMethod("PUT") val userPassword = read(Path(sonatypeLogin)).trim val encoding = new sun.misc.BASE64Encoder().encode(userPassword.getBytes) - httpCon.setRequestProperty("Authorization", "Basic " + encoding) + httpCon.setRequestProperty("Authorization", "Basic " ++ encoding) httpCon.setRequestProperty("Content-Type", "application/binary") httpCon.getOutputStream.write( fileContents @@ -389,7 +383,7 @@ final class Lib(logger: Logger) extends Stage1Lib(logger) with Scaffold{ files.map{ file => - if(file.isFile) new File( dirname(file.toString) ) + if(file.isFile) dirname(file) else file }.distinct.map{ file => val watchableFile = new WatchableFile(file) @@ -411,7 +405,7 @@ final class Lib(logger: Logger) extends Stage1Lib(logger) with Scaffold{ .filterNot(_.kind == StandardWatchEventKind.OVERFLOW) .map(_.context.toString) .map(new File(_)) - changedFiles.foreach( f => logger.loop("Changed: "+f) ) + changedFiles.foreach( f => logger.loop( "Changed: " ++ f.toString ) ) changedFiles.collect(action) key.reset } diff --git a/stage2/PackageBuild.scala b/stage2/PackageBuild.scala new file mode 100644 index 0000000..96c7b6f --- /dev/null +++ b/stage2/PackageBuild.scala @@ -0,0 +1,27 @@ +package cbt +import java.io.File +import java.net.URL +import scala.collection.immutable.Seq +abstract class PackageBuild(context: Context) extends Build(context) with ArtifactInfo{ + def `package`: Seq[File] = lib.concurrently( enableConcurrency )( + Seq(() => jar, () => docJar, () => srcJar) + )( _() ) + + private object cacheJarBasicBuild extends Cache[File] + def jar: File = cacheJarBasicBuild{ + lib.jar( artifactId, version, compile, jarTarget ) + } + + private object cacheSrcJarBasicBuild extends Cache[File] + def srcJar: File = cacheSrcJarBasicBuild{ + lib.srcJar(sources, artifactId, version, scalaTarget) + } + + private object cacheDocBasicBuild extends Cache[File] + def docJar: File = cacheDocBasicBuild{ + lib.docJar( sources, dependencyClasspath, apiTarget, jarTarget, artifactId, version, scalacOptions ) + } + + override def jars = jar +: dependencyJars + override def exportedJars: Seq[File] = Seq(jar) +} diff --git a/stage2/PublishBuild.scala b/stage2/PublishBuild.scala new file mode 100644 index 0000000..e4e8fd7 --- /dev/null +++ b/stage2/PublishBuild.scala @@ -0,0 +1,41 @@ +package cbt +import java.io.File +import java.net.URL +import scala.collection.immutable.Seq + +abstract class PublishBuild(context: Context) extends PackageBuild(context){ + def name = artifactId + def description: String + def url: URL + def developers: Seq[Developer] + def licenses: Seq[License] + def scmUrl: String + def scmConnection: String + def pomExtra: Seq[scala.xml.Node] = Seq() + + // ========== package ========== + + /** put additional xml that should go into the POM file in here */ + def pom: File = lib.pom( + groupId = groupId, + artifactId = artifactId, + version = version, + name = name, + description = description, + url = url, + developers = developers, + licenses = licenses, + scmUrl = scmUrl, + scmConnection = scmConnection, + dependencies = dependencies, + pomExtra = pomExtra, + jarTarget = jarTarget + ) + + // ========== publish ========== + final protected def releaseFolder = s"/${groupId.replace(".","/")}/$artifactId/$version/" + def snapshotUrl = new URL("https://oss.sonatype.org/content/repositories/snapshots") + def releaseUrl = new URL("https://oss.sonatype.org/service/local/staging/deploy/maven2") + def publishSnapshot: Unit = lib.publishSnapshot(sourceFiles, pom +: `package`, snapshotUrl ++ releaseFolder ) + def publishSigned: Unit = lib.publishSigned(sourceFiles, pom +: `package`, releaseUrl ++ releaseFolder ) +} diff --git a/stage2/Scaffold.scala b/stage2/Scaffold.scala index 00c8706..18b2cc3 100644 --- a/stage2/Scaffold.scala +++ b/stage2/Scaffold.scala @@ -49,7 +49,7 @@ class Build(context: Context) extends BuildBuild(context){ override def scalaVersion: String = "2.11.7" override def dependencies = super.dependencies ++ Seq( - BuildDependency( projectDirectory + "/../build-shared/") + BuildDependency( projectDirectory.parent ++ "/build-shared") // , "com.lihaoyi" %% "ammonite-ops" % "0.5.5" ) } @@ -85,7 +85,7 @@ class Build(context: Context) extends BuildBuild(context){ override def scalaVersion: String = "2.11.7" override def dependencies = super.dependencies ++ Seq( - BuildDependency( projectDirectory + "/../../build-shared/") + BuildDependency( projectDirectory.parent.parent ++ "/build-shared") // , "com.lihaoyi" %% "ammonite-ops" % "0.5.5" ) } @@ -132,9 +132,9 @@ trait BuildShared extends BasicBuild{ generatedFiles.map{ case ( fileName, code ) => scala.util.Try{ - write( Path(projectDirectory+"/"+fileName), code ) + write( Path( projectDirectory.string ++ "/" ++ fileName ), code ) import scala.Console._ - println( GREEN + "Created " + fileName + RESET ) + println( GREEN ++ "Created " ++ fileName ++ RESET ) } }.foreach( _.recover{ diff --git a/stage2/Stage2.scala b/stage2/Stage2.scala index c6783d4..ed63cf1 100644 --- a/stage2/Stage2.scala +++ b/stage2/Stage2.scala @@ -27,7 +27,7 @@ object Stage2{ } val task = argsV.lift( taskIndex ) - val context = Context( argsV(0), argsV.drop( taskIndex + 1 ), logger ) + val context = Context( new File(argsV(0)), argsV.drop( taskIndex + 1 ), logger ) val first = lib.loadRoot( context ) val build = first.finalBuild @@ -38,7 +38,7 @@ object Stage2{ val triggerCbtFiles = Seq( nailgun, stage1, stage2 ).map(lib.realpath _) val allTriggerFiles = triggerFiles ++ triggerCbtFiles - logger.loop("Looping change detection over:\n - "+allTriggerFiles.mkString("\n - ")) + logger.loop("Looping change detection over:\n - "++allTriggerFiles.mkString("\n - ")) lib.watch(allTriggerFiles) { case file if triggerCbtFiles.exists(file.toString startsWith _.toString) => diff --git a/stage2/dependencies.scala b/stage2/dependencies.scala deleted file mode 100644 index 8ed36eb..0000000 --- a/stage2/dependencies.scala +++ /dev/null @@ -1,36 +0,0 @@ -package cbt -import java.io.File -import scala.collection.immutable.Seq -/* -sealed abstract class ProjectProxy extends Ha{ - protected def delegate: ProjectMetaData - def artifactId: String = delegate.artifactId - def groupId: String = delegate.groupId - def version: String = delegate.version - def exportedClasspath = delegate.exportedClasspath - def dependencies = Seq(delegate) -} -*/ -trait TriggerLoop extends Dependency{ - def triggerLoopFiles: Seq[File] -} -/** 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+")" - 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()) ) - lazy val build = root.finalBuild - def exportedClasspath = ClassPath(Seq()) - def exportedJars = Seq() - def dependencies = Seq(build) - def triggerLoopFiles = root.triggerLoopFiles - final val updated = build.updated -} -/* -case class DependencyOr(first: BuildDependency, second: MavenDependency) extends ProjectProxy with BuildDependencyBase{ - val isFirst = new File(first.context.cwd).exists - def triggerLoopFiles = if(isFirst) first.triggerLoopFiles else Seq() - protected val delegate = if(isFirst) first else second -} -*/ \ No newline at end of file diff --git a/stage2/mixins.scala b/stage2/mixins.scala index 4d72325..2b38cdf 100644 --- a/stage2/mixins.scala +++ b/stage2/mixins.scala @@ -3,15 +3,15 @@ package mixins import scala.collection.immutable.Seq import java.io._ trait Test extends Build{ - lazy val testedBuild = BuildDependency(projectDirectory+"/../") + lazy val testedBuild = BuildDependency( projectDirectory.parent ) override def dependencies = Seq( testedBuild ) ++ super.dependencies override def scalaVersion = testedBuild.build.scalaVersion } trait Sbt extends Build{ - override def sources = Seq(new File(projectDirectory+"/src/main/scala/")) + override def sources = Seq( projectDirectory ++ "/src/main/scala" ) } trait SbtTest extends Test{ - override def sources = Vector(new File(projectDirectory+"/../src/test/scala")) + override def sources = Vector( projectDirectory.parent ++ "/src/test/scala" ) } trait ScalaTest extends Build with Test{ def scalaTestVersion: String @@ -24,8 +24,8 @@ trait ScalaTest extends Build with Test{ override def cacheDependencyClassLoader = false override def run: ExitCode = { - val discoveryPath = compile.toString+"/" - context.logger.lib("discoveryPath: "+discoveryPath) + val discoveryPath = compile.toString++"/" + context.logger.lib("discoveryPath: " ++ discoveryPath) lib.runMain( "org.scalatest.tools.Runner", Seq("-R", discoveryPath, "-oF") ++ context.args.drop(1), diff --git a/test/test.scala b/test/test.scala index 3befa4a..feab89f 100644 --- a/test/test.scala +++ b/test/test.scala @@ -2,68 +2,71 @@ import cbt._ import cbt.paths._ import scala.collection.immutable.Seq +// micro framework object Main{ - // micro framework - var successes = 0 - var failures = 0 - def assert(condition: Boolean, msg: String = "")(implicit logger: Logger) = { - scala.util.Try{ - Predef.assert(condition, msg) - }.map{ _ => - print(".") - successes += 1 - }.recover{ - case e: AssertionError => - println("FAILED") - e.printStackTrace - failures += 1 - }.get - } - - def runCbt(path: String, args: Seq[String])(implicit logger: Logger): Result = { - import java.io._ - 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.inheritIO.start - p.waitFor - val berr = new BufferedReader(new InputStreamReader(p.getErrorStream)); - val bout = new BufferedReader(new InputStreamReader(p.getInputStream)); - p.waitFor - import collection.JavaConversions._ - val err = Stream.continually(berr.readLine()).takeWhile(_ != null).mkString("\n") - val out = Stream.continually(bout.readLine()).takeWhile(_ != null).mkString("\n") - Result(out, err, p.exitValue == 0) - } - case class Result(out: String, err: String, exit0: Boolean) - def assertSuccess(res: Result)(implicit logger: Logger) = { - assert(res.exit0,res.toString) - } - - // tests - def usage(path: String)(implicit logger: Logger) = { - val usageString = "Methods provided by CBT" - val res = runCbt(path, Seq()) - logger.test(res.toString) - assertSuccess(res) - assert(res.out == "", "#"+res.out+"#") - assert(res.err contains usageString, res.err) - } - 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 = { - implicit val logger: Logger = new Init(args).logger + val init = new Init(args) + implicit val logger: Logger = init.logger - System.err.println("Running tests "+args.toList) + var successes = 0 + var failures = 0 + def assert(condition: Boolean, msg: String = "")(implicit logger: Logger) = { + scala.util.Try{ + Predef.assert(condition, "["++msg++"]") + }.map{ _ => + print(".") + successes += 1 + }.recover{ + case e: AssertionError => + println("FAILED") + e.printStackTrace + failures += 1 + }.get + } + + def runCbt(path: String, args: Seq[String])(implicit logger: Logger): Result = { + import java.io._ + val allArgs: Seq[String] = ((cbtHome.string ++ "/cbt") +: "direct" +: (args ++ init.propsRaw)) + logger.test(allArgs.toString) + val pb = new ProcessBuilder( allArgs :_* ) + pb.directory(cbtHome ++ ("/test/" ++ path)) + 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 + import collection.JavaConversions._ + val err = Stream.continually(berr.readLine()).takeWhile(_ != null).mkString("\n") + val out = Stream.continually(bout.readLine()).takeWhile(_ != null).mkString("\n") + Result(out, err, p.exitValue == 0) + } + case class Result(out: String, err: String, exit0: Boolean) + def assertSuccess(res: Result)(implicit logger: Logger) = { + assert(res.exit0, res.toString) + } + + // tests + def usage(path: String)(implicit logger: Logger) = { + val usageString = "Methods provided by CBT" + val res = runCbt(path, Seq()) + logger.test(res.toString) + assertSuccess(res) + assert(res.out == "", res.toString) + assert(res.err contains usageString, res.toString) + } + def compile(path: String)(implicit logger: Logger) = { + val res = runCbt(path, Seq("compile")) + assertSuccess(res) + // assert(res.err == "", res.err) // FIXME: enable this + } + + logger.test( "Running tests " ++ args.toList.toString ) + usage("nothing") compile("nothing") { - val noContext = Context(cbtHome + "/test/" + "nothing",Seq(),logger) + val noContext = Context(cbtHome ++ "/test/nothing", Seq(), logger) val b = new Build(noContext){ override def dependencies = Seq( MavenDependency("net.incongru.watchservice","barbary-watchservice","1.0")(logger), @@ -71,11 +74,11 @@ object Main{ ) } val cp = b.classpath - assert(cp.strings.distinct == cp.strings, "duplicates in classpath: "+cp) + assert(cp.strings.distinct == cp.strings, "duplicates in classpath: " ++ cp.string) } System.err.println(" DONE!") - System.err.println(successes+" succeeded, "+ failures+" failed" ) + System.err.println( successes.toString ++ " succeeded, "++ failures.toString ++ " failed" ) if(failures > 0) System.exit(1) else System.exit(0) } } -- cgit v1.2.3