aboutsummaryrefslogtreecommitdiff
path: root/stage2
diff options
context:
space:
mode:
authorJan Christopher Vogt <oss.nsp@cvogt.org>2016-04-02 16:06:40 -0400
committerJan Christopher Vogt <oss.nsp@cvogt.org>2016-04-02 16:06:40 -0400
commit63b54f79c10854e38b2a4a43ee39f508458e280f (patch)
tree6a5791efedc2d297cfac1ad8bbaac0b090105149 /stage2
parent16b02cf34078113c833225297b686752aa26b407 (diff)
parentefe68c7e710aa8c54144715408b7faca36f52c27 (diff)
downloadcbt-63b54f79c10854e38b2a4a43ee39f508458e280f.tar.gz
cbt-63b54f79c10854e38b2a4a43ee39f508458e280f.tar.bz2
cbt-63b54f79c10854e38b2a4a43ee39f508458e280f.zip
Rewrite CBT's classloading and dependency classloaders, fetch zinc early and various smaller changes
Rewrite CBT's classloading and dependency classloaders, fetch zinc early and various smaller changes
Diffstat (limited to 'stage2')
-rw-r--r--stage2/AdminStage2.scala13
-rw-r--r--stage2/AdminTasks.scala109
-rw-r--r--stage2/BasicBuild.scala52
-rw-r--r--stage2/BuildBuild.scala8
-rw-r--r--stage2/BuildDependency.scala2
-rw-r--r--stage2/GitDependency.scala6
-rw-r--r--stage2/Lib.scala234
-rw-r--r--stage2/NameTransformer.scala161
-rw-r--r--stage2/PackageBuild.scala22
-rw-r--r--stage2/Scaffold.scala7
-rw-r--r--stage2/Stage2.scala31
-rw-r--r--stage2/mixins.scala5
12 files changed, 451 insertions, 199 deletions
diff --git a/stage2/AdminStage2.scala b/stage2/AdminStage2.scala
index d923b22..883b5ed 100644
--- a/stage2/AdminStage2.scala
+++ b/stage2/AdminStage2.scala
@@ -1,13 +1,12 @@
package cbt
import java.io._
-object AdminStage2{
- def main(_args: Array[String]) = {
- val args = _args.drop(1).dropWhile(Seq("admin","direct") contains _)
- val init = new Init(args)
- val lib = new Lib(init.logger)
- val adminTasks = new AdminTasks(lib, args, new File(_args(0)))
+object AdminStage2 extends Stage2Base{
+ def run( _args: Stage2Args ): Unit = {
+ val args = _args.args.dropWhile(Seq("admin","direct") contains _)
+ val lib = new Lib(_args.logger)
+ val adminTasks = new AdminTasks(lib, args, _args.cwd)
new lib.ReflectObject(adminTasks){
- def usage: String = "Available methods: " ++ lib.taskNames(subclassType).mkString(" ")
+ def usage: String = "Available methods: " ++ lib.taskNames(adminTasks.getClass).mkString(" ")
}.callNullary(args.lift(0))
}
}
diff --git a/stage2/AdminTasks.scala b/stage2/AdminTasks.scala
index e7fc78b..069b712 100644
--- a/stage2/AdminTasks.scala
+++ b/stage2/AdminTasks.scala
@@ -1,35 +1,132 @@
package cbt
import scala.collection.immutable.Seq
-import java.io._
-class AdminTasks(lib: Lib, args: Array[String], cwd: File){
+import java.io.{Console=>_,_}
+import java.nio.file._
+class AdminTasks(lib: Lib, args: Seq[String], cwd: File){
implicit val logger: Logger = lib.logger
def resolve = {
ClassPath.flatten(
args(1).split(",").toVector.map{
d =>
val v = d.split(":")
- new JavaDependency(v(0),v(1),v(2))(lib.logger).classpath
+ new JavaDependency(v(0),v(1),v(2)).classpath
}
)
}
+ def dependencyTree = {
+ args(1).split(",").toVector.map{
+ d =>
+ val v = d.split(":")
+ new JavaDependency(v(0),v(1),v(2)).dependencyTree
+ }.mkString("\n\n")
+ }
def amm = ammonite
def ammonite = {
val version = args.lift(1).getOrElse(constants.scalaVersion)
val scalac = new ScalaCompilerDependency( version )
val d = JavaDependency(
- "com.lihaoyi","ammonite-repl_2.11.7",args.lift(1).getOrElse("0.5.6")
+ "com.lihaoyi","ammonite-repl_2.11.7",args.lift(1).getOrElse("0.5.7")
)
// FIXME: this does not work quite yet, throws NoSuchFileException: /ammonite/repl/frontend/ReplBridge$.class
lib.runMain(
- "ammonite.repl.Main", Seq(), d.classLoader
+ "ammonite.repl.Main", Seq(), d.classLoader(new ClassLoaderCache(logger))
)
}
def scala = {
val version = args.lift(1).getOrElse(constants.scalaVersion)
val scalac = new ScalaCompilerDependency( version )
lib.runMain(
- "scala.tools.nsc.MainGenericRunner", Seq("-cp", scalac.classpath.string), scalac.classLoader
+ "scala.tools.nsc.MainGenericRunner", Seq("-cp", scalac.classpath.string), scalac.classLoader(new ClassLoaderCache(logger))
)
}
def scaffoldBasicBuild: Unit = lib.scaffoldBasicBuild( cwd )
+ def cbtEarlyDependencies = {
+ val scalaVersion = args.lift(1).getOrElse(constants.scalaVersion)
+ val scalaMajorVersion = scalaVersion.split("\\.").take(2).mkString(".")
+ val scalaXmlVersion = args.lift(2).getOrElse(constants.scalaXmlVersion)
+ val zincVersion = args.lift(3).getOrElse(constants.zincVersion)
+ /*
+ def tree(d: JavaDependency, indent: Int): String ={
+ val dependencies = {
+ if( d.dependencies.nonEmpty ){
+ d.dependencies.map{
+ case d: JavaDependency => tree(d,indent + 1)
+ }.mkString(",\n" ++ ( " " * indent ),",\n" ++ ( " " * indent ), "")
+ } else ""
+ }
+ (
+ s"""new EarlyDependency( "${d.groupId}", "${d.artifactId}", "${d.version}", "${d.jarSha1}"$dependencies)"""
+ )
+ }*/
+ val scalaDeps = Seq(
+ JavaDependency("org.scala-lang","scala-reflect",scalaVersion),
+ JavaDependency("org.scala-lang","scala-compiler",scalaVersion)
+ )
+
+ val scalaXml = Dependencies(
+ JavaDependency("org.scala-lang.modules","scala-xml_"+scalaMajorVersion,scalaXmlVersion),
+ JavaDependency("org.scala-lang","scala-library",scalaVersion)
+ )
+
+ val zinc = JavaDependency("com.typesafe.zinc","zinc",zincVersion)
+ println(zinc.dependencyTree)
+
+ def valName(dep: JavaDependency) = {
+ val words = dep.artifactId.split("_").head.split("-")
+ words(0) ++ words.drop(1).map(s => s(0).toString.toUpperCase ++ s.drop(1)).mkString ++ "_" ++ dep.version.replace(".","_") ++ "_"
+ }
+
+ def vals(d: JavaDependency) = s""" """
+
+ def jarVal(dep: JavaDependency) = "_" + valName(dep) +"Jar"
+ def transitive(dep: Dependency) = (dep +: dep.transitiveDependencies.reverse).collect{case d: JavaDependency => d}
+ def codeEach(dep: Dependency) = {
+ transitive(dep).tails.map(_.reverse).toVector.reverse.drop(1).map{
+ deps =>
+ val d = deps.last
+ val parents = deps.dropRight(1)
+ val parentString = if(parents.isEmpty) "" else ( ", " ++ valName(parents.last) )
+ val n = valName(d)
+ s"""
+ // ${d.groupId}:${d.artifactId}:${d.version}
+ download(new URL(MAVEN_URL + "${d.basePath}.jar"), Paths.get(${n}File), "${d.jarSha1}");
+ ClassLoader $n = cachePut(
+ classLoader( ${n}File$parentString ),
+ ${deps.sortBy(_.jar).map(valName(_)+"File").mkString(", ")}
+ );"""
+ }
+ }
+ val assignments = codeEach(zinc) ++ codeEach(scalaXml)
+ //{ case (name, dep) => s"$name =\n ${tree(dep, 4)};" }.mkString("\n\n ")
+ val code = s"""// This file was auto-generated using `cbt admin cbtEarlyDependencies`
+package cbt;
+import java.io.*;
+import java.nio.file.*;
+import java.net.*;
+import java.security.*;
+import static cbt.NailgunLauncher.*;
+
+class EarlyDependencies{
+
+ /** ClassLoader for stage1 */
+ ClassLoader stage1;
+ /** ClassLoader for zinc */
+ ClassLoader zinc;
+
+${(scalaDeps ++ transitive(scalaXml) ++ transitive(zinc)).map(d => s""" String ${valName(d)}File = MAVEN_CACHE + "${d.basePath}.jar";""").mkString("\n")}
+
+ public EarlyDependencies() throws MalformedURLException, IOException, NoSuchAlgorithmException{
+${scalaDeps.map(d => s""" download(new URL(MAVEN_URL + "${d.basePath}.jar"), Paths.get(${valName(d)}File), "${d.jarSha1}");""").mkString("\n")}
+${assignments.mkString("\n")}
+
+ stage1 = scalaXml_${scalaXmlVersion.replace(".","_")}_;
+
+ zinc = zinc_${zincVersion.replace(".","_")}_;
+ }
+}
+"""
+ val file = paths.nailgun ++ ("/" ++ "EarlyDependencies.java")
+ Files.write( file.toPath, code.getBytes )
+ println( Console.GREEN ++ "Wrote " ++ file.string ++ Console.RESET )
+ }
}
diff --git a/stage2/BasicBuild.scala b/stage2/BasicBuild.scala
index 2f90197..9ed8c26 100644
--- a/stage2/BasicBuild.scala
+++ b/stage2/BasicBuild.scala
@@ -2,7 +2,6 @@ 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
@@ -10,15 +9,13 @@ 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
implicit final val logger: Logger = context.logger
+ implicit final val classLoaderCache: ClassLoaderCache = context.classLoaderCache
override final protected val lib: Lib = new Lib(logger)
// ========== general stuff ==========
@@ -26,7 +23,7 @@ class Build(val context: Context) extends Dependency with TriggerLoop{
def enableConcurrency = false
final def projectDirectory: File = lib.realpath(context.cwd)
assert( projectDirectory.exists, "projectDirectory does not exist: " ++ projectDirectory.string )
- final def usage: Unit = new lib.ReflectBuild(this).usage
+ final def usage: String = lib.usage(this.getClass, context)
// ========== meta data ==========
@@ -51,6 +48,12 @@ class Build(val context: Context) extends Dependency with TriggerLoop{
def apiTarget: File = scalaTarget ++ "/api"
/** directory where the class files should be put (in package directories) */
def compileTarget: File = scalaTarget ++ "/classes"
+ /**
+ File which cbt uses to determine if it needs to trigger an incremental re-compile.
+ Last modified date is the time when the last successful compilation started.
+ Contents is the cbt version git hash.
+ */
+ def compileStatusFile: File = compileTarget ++ ".last-success"
/** 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)
@@ -113,7 +116,7 @@ class Build(val context: Context) extends Dependency with TriggerLoop{
override def dependencyClasspath : ClassPath = ClassPath(localJars) ++ super.dependencyClasspath
override def dependencyJars : Seq[File] = localJars ++ super.dependencyJars
- def exportedClasspath : ClassPath = ClassPath(Seq(compile))
+ def exportedClasspath : ClassPath = ClassPath(compile.toSeq:_*)
def targetClasspath = ClassPath(Seq(compileTarget))
def exportedJars: Seq[File] = Seq()
// ========== compile, run, test ==========
@@ -121,38 +124,25 @@ class Build(val context: Context) extends Dependency with TriggerLoop{
/** 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 needsUpdateCache extends Cache[Boolean]
+ def needsUpdate: Boolean = {
+ needsUpdateCache(
+ lib.needsUpdate( sourceFiles, compileStatusFile )
+ || transitiveDependencies.exists(_.needsUpdate)
+ )
}
- private object compileCache extends Cache[File]
- def compile: File = compileCache{
+ private object compileCache extends Cache[Option[File]]
+ def compile: Option[File] = compileCache{
lib.compile(
- updated,
- sourceFiles, compileTarget, dependencyClasspath, scalacOptions,
- zincVersion = zincVersion, scalaVersion = scalaVersion
+ needsUpdate,
+ sourceFiles, compileTarget, compileStatusFile, dependencyClasspath, scalacOptions,
+ context.classLoaderCache, zincVersion = zincVersion, scalaVersion = scalaVersion
)
}
def runClass: String = "Main"
- def run: ExitCode = lib.runMainIfFound( runClass, context.args, classLoader )
+ def run: ExitCode = lib.runMainIfFound( runClass, context.args, classLoader(context.classLoaderCache) )
def test: ExitCode = lib.test(context)
diff --git a/stage2/BuildBuild.scala b/stage2/BuildBuild.scala
index 5e0f5d3..9746d8c 100644
--- a/stage2/BuildBuild.scala
+++ b/stage2/BuildBuild.scala
@@ -8,10 +8,14 @@ class BuildBuild(context: Context) extends Build(context){
val managedBuild = {
val managedContext = context.copy( cwd = managedBuildDirectory )
val cl = new cbt.URLClassLoader(
- classpath,
+ exportedClasspath,
classOf[BuildBuild].getClassLoader // FIXME: this looks wrong. Should be ClassLoader.getSystemClassLoader but that crashes
)
- lib.create( lib.buildClassName )( managedContext )( cl ).asInstanceOf[Build]
+ cl
+ .loadClass(lib.buildClassName)
+ .getConstructor(classOf[Context])
+ .newInstance(managedContext)
+ .asInstanceOf[Build]
}
override def triggerLoopFiles = super.triggerLoopFiles ++ managedBuild.triggerLoopFiles
override def finalBuild = managedBuild.finalBuild
diff --git a/stage2/BuildDependency.scala b/stage2/BuildDependency.scala
index 84a0100..e3a01c7 100644
--- a/stage2/BuildDependency.scala
+++ b/stage2/BuildDependency.scala
@@ -25,7 +25,7 @@ case class BuildDependency(context: Context) extends TriggerLoop{
def exportedJars = Seq()
def dependencies = Seq(build)
def triggerLoopFiles = root.triggerLoopFiles
- final val updated = build.updated
+ override final val needsUpdate = build.needsUpdate
def targetClasspath = ClassPath(Seq())
}
/*
diff --git a/stage2/GitDependency.scala b/stage2/GitDependency.scala
index c3e38b6..59de98a 100644
--- a/stage2/GitDependency.scala
+++ b/stage2/GitDependency.scala
@@ -7,7 +7,7 @@ import org.eclipse.jgit.lib.Ref
case class GitDependency(
url: String, ref: String // example: git://github.com/cvogt/cbt.git#<some-hash>
-)(implicit val logger: Logger) extends Dependency{
+)(implicit val logger: Logger, classLoaderCache: ClassLoaderCache ) extends Dependency{
override def lib = new Lib(logger)
// TODO: add support for authentication via ssh and/or https
@@ -37,7 +37,7 @@ case class GitDependency(
}
val managedBuild = lib.loadDynamic(
- Context( cwd = checkoutDirectory, args = Seq(), logger )
+ Context( cwd = checkoutDirectory, args = Seq(), logger, classLoaderCache )
)
Seq( managedBuild )
}
@@ -45,5 +45,5 @@ case class GitDependency(
def exportedClasspath = ClassPath(Seq())
def exportedJars = Seq()
private[cbt] def targetClasspath = exportedClasspath
- def updated: Boolean = false
+ def needsUpdate: Boolean = false
}
diff --git a/stage2/Lib.scala b/stage2/Lib.scala
index 60e7dd4..dd4a12f 100644
--- a/stage2/Lib.scala
+++ b/stage2/Lib.scala
@@ -8,13 +8,11 @@ import java.nio.file.{Path =>_,_}
import java.nio.file.Files.readAllBytes
import java.security.MessageDigest
import java.util.jar._
+import java.lang.reflect.Method
import scala.collection.immutable.Seq
-import scala.reflect.runtime.{universe => ru}
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)
@@ -55,28 +53,18 @@ final class Lib(logger: Logger) extends Stage1Lib(logger) with Scaffold{
}
}
- def compile(
- updated: Boolean,
- sourceFiles: Seq[File], compileTarget: File, dependenyClasspath: ClassPath,
- compileArgs: Seq[String], zincVersion: String, scalaVersion: String
- ): File = {
- if(sourceFiles.nonEmpty)
- lib.zinc(
- updated, sourceFiles, compileTarget, dependenyClasspath, compileArgs
- )( zincVersion = zincVersion, scalaVersion = scalaVersion )
- compileTarget
- }
-
- def srcJar(sources: Seq[File], artifactId: String, version: String, jarTarget: File): File = {
- val file = jarTarget ++ ("/"++artifactId++"-"++version++"-sources.jar")
- lib.jarFile(file, sources)
- file
+ def srcJar(sourceFiles: Seq[File], artifactId: String, version: String, jarTarget: File): Option[File] = {
+ lib.jarFile(
+ jarTarget ++ ("/"++artifactId++"-"++version++"-sources.jar"),
+ sourceFiles
+ )
}
- def jar(artifactId: String, version: String, compileTarget: File, jarTarget: File): File = {
- val file = jarTarget ++ ("/"++artifactId++"-"++version++".jar")
- lib.jarFile(file, Seq(compileTarget))
- file
+ def jar(artifactId: String, version: String, compileTarget: File, jarTarget: File): Option[File] = {
+ lib.jarFile(
+ jarTarget ++ ("/"++artifactId++"-"++version++".jar"),
+ Seq(compileTarget)
+ )
}
def docJar(
@@ -87,29 +75,31 @@ final class Lib(logger: Logger) extends Stage1Lib(logger) with Scaffold{
jarTarget: File,
artifactId: String,
version: String,
- compileArgs: Seq[String]
- ): File = {
- mkdir(Path(apiTarget))
- if(sourceFiles.nonEmpty){
+ compileArgs: Seq[String],
+ classLoaderCache: ClassLoaderCache
+ ): Option[File] = {
+ if(sourceFiles.isEmpty){
+ None
+ } else {
+ apiTarget.mkdirs
val args = Seq(
// FIXME: can we use compiler dependency here?
"-cp", dependencyClasspath.string, // FIXME: does this break for builds that don't have scalac dependencies?
"-d", apiTarget.toString
) ++ compileArgs ++ sourceFiles.map(_.toString)
logger.lib("creating docs for source files "+args.mkString(", "))
- trapExitCode{
- redirectOutToErr{
- runMain(
- "scala.tools.nsc.ScalaDoc",
- args,
- ScalaDependencies(scalaVersion)(logger).classLoader
- )
- }
+ redirectOutToErr{
+ runMain(
+ "scala.tools.nsc.ScalaDoc",
+ args,
+ ScalaDependencies(scalaVersion)(logger).classLoader(classLoaderCache)
+ )
}
+ lib.jarFile(
+ jarTarget ++ ("/"++artifactId++"-"++version++"-javadoc.jar"),
+ Vector(apiTarget)
+ )
}
- val docJar = jarTarget ++ ("/"++artifactId++"-"++version++"-javadoc.jar")
- lib.jarFile(docJar, Vector(apiTarget))
- docJar
}
def test( context: Context ): ExitCode = {
@@ -128,60 +118,66 @@ final class Lib(logger: Logger) extends Stage1Lib(logger) with Scaffold{
}
// task reflection helpers
- import ru._
- 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)
- .filter(_.isMethod)
- .map(_.asMethod)
- .filter(_.paramLists.flatten.size == 0)
- .filterNot(taskName(_) contains "$")
- .filterNot(t => anyRefMembers contains taskName(t))
- }
+ def tasks(cls:Class[_]): Map[String, Method] =
+ Stream
+ .iterate(cls.asInstanceOf[Class[Any]])(_.getSuperclass)
+ .takeWhile(_ != null)
+ .toVector
+ .dropRight(1) // drop Object
+ .reverse
+ .flatMap(
+ c =>
+ c
+ .getDeclaredMethods
+ .filterNot( _.getName contains "$" )
+ .filter{ m =>
+ java.lang.reflect.Modifier.isPublic(m.getModifiers)
+ }
+ .filter( _.getParameterCount == 0 )
+ .map(m => NameTransformer.decode(m.getName) -> m)
+ ).toMap
- class ReflectBuild(val build: Build) extends ReflectObject(build){
- def usage: String = {
- val baseTasks = lib.taskNames(ru.typeOf[Build])
- val thisTasks = lib.taskNames(subclassType) diff baseTasks
+ def taskNames(cls: Class[_]): Seq[String] = tasks(cls).keys.toVector.sorted
+
+ def usage(buildClass: Class[_], context: Context): String = {
+ val baseTasks = lib.taskNames(classOf[Build])
+ val thisTasks = lib.taskNames(buildClass) diff baseTasks
+ (
(
- (
- if( thisTasks.nonEmpty ){
- s"""Methods provided by Build ${build.context.cwd}
+ if( thisTasks.nonEmpty ){
+ s"""Methods provided by Build ${context}
${thisTasks.mkString(" ")}
"""
- } else ""
- ) ++ s"""Methods provided by CBT (but possibly overwritten)
+ } else ""
+ ) ++ s"""Methods provided by CBT (but possibly overwritten)
${baseTasks.mkString(" ")}"""
) ++ "\n"
- }
}
+ class ReflectBuild[T:scala.reflect.ClassTag](build: Build) extends ReflectObject(build){
+ def usage = lib.usage(build.getClass, build.context)
+ }
abstract class ReflectObject[T:scala.reflect.ClassTag](obj: T){
- lazy val mirror = ru.runtimeMirror(obj.getClass.getClassLoader)
- lazy val subclassType = mirror.classSymbol(obj.getClass).toType
def usage: String
def callNullary( taskName: Option[String] ): Unit = {
- taskName
- .map{ n => subclassType.member(ru.TermName(n).encodedName) }
- .filter(_ != ru.NoSymbol)
- .flatMap(toTask _)
- .map{ methodSymbol =>
- val result = mirror.reflect(obj).reflectMethod(methodSymbol)()
-
+ val ts = tasks(obj.getClass)
+ taskName.map( NameTransformer.encode ).flatMap(ts.get).map{ method =>
+ val result: Option[Any] = Option(method.invoke(obj)) // null in case of Unit
+ result.flatMap{
+ case v: Option[_] => v
+ case other => Some(other)
+ }.map{
+ value =>
// Try to render console representation. Probably not the best way to do this.
- scala.util.Try( result.getClass.getDeclaredMethod("toConsole") ) match {
- case scala.util.Success(m) =>
- println(m.invoke(result))
+ scala.util.Try( value.getClass.getDeclaredMethod("toConsole") ) match {
+ case scala.util.Success(toConsole) =>
+ println(toConsole.invoke(value))
- case scala.util.Failure(e) if e.getMessage contains "toConsole" =>
- result match {
- case () => ""
+ case scala.util.Failure(e) if Option(e.getMessage).getOrElse("") contains "toConsole" =>
+ value match {
case ExitCode(code) => System.exit(code)
case other => println( other.toString ) // no method .toConsole, using to String
}
@@ -189,16 +185,17 @@ final class Lib(logger: Logger) extends Stage1Lib(logger) with Scaffold{
case scala.util.Failure(e) =>
throw e
}
- }.getOrElse{
- taskName.foreach{ n =>
- System.err.println(s"Method not found: $n")
- System.err.println("")
- }
- System.err.println(usage)
- taskName.foreach{ _ =>
- ExitCode.Failure
- }
+ }.getOrElse("")
+ }.getOrElse{
+ taskName.foreach{ n =>
+ System.err.println(s"Method not found: $n")
+ System.err.println("")
}
+ System.err.println(usage)
+ taskName.foreach{ _ =>
+ ExitCode.Failure
+ }
+ }
}
}
@@ -207,35 +204,41 @@ final class Lib(logger: Logger) extends Stage1Lib(logger) with Scaffold{
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.string)
- val manifest = new Manifest
- manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0")
- val jar = new JarOutputStream(new FileOutputStream(jarFile.toString), manifest)
-
- val names = for {
- base <- files.filter(_.exists).map(realpath)
- file <- listFilesRecursive(base) if file.isFile
- } yield {
- val name = if(base.isDirectory){
- file.toString stripPrefix base.toString
- } else file.toString
- val entry = new JarEntry( name )
- entry.setTime(file.lastModified)
- jar.putNextEntry(entry)
- jar.write( readAllBytes( Paths.get(file.toString) ) )
- jar.closeEntry
- name
- }
+ def jarFile( jarFile: File, files: Seq[File] ): Option[File] = {
+ if( files.isEmpty ){
+ None
+ } else {
+ 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)
+
+ val names = for {
+ base <- files.filter(_.exists).map(realpath)
+ file <- listFilesRecursive(base) if file.isFile
+ } yield {
+ val name = if(base.isDirectory){
+ file.toString stripPrefix base.toString
+ } else file.toString
+ val entry = new JarEntry( name )
+ entry.setTime(file.lastModified)
+ jar.putNextEntry(entry)
+ jar.write( readAllBytes( Paths.get(file.toString) ) )
+ jar.closeEntry
+ name
+ }
- val duplicateFiles = (names diff names.distinct).distinct
- assert(
- duplicateFiles.isEmpty,
- s"Conflicting file names when trying to create $jarFile: "++duplicateFiles.mkString(", ")
- )
+ val duplicateFiles = (names diff names.distinct).distinct
+ assert(
+ duplicateFiles.isEmpty,
+ s"Conflicting file names when trying to create $jarFile: "++duplicateFiles.mkString(", ")
+ )
- jar.close
- logger.lib("Done packaging " ++ jarFile.toString)
+ jar.close
+ logger.lib("Done packaging " ++ jarFile.toString)
+
+ Some(jarFile)
+ }
}
lazy val passphrase =
@@ -321,8 +324,9 @@ final class Lib(logger: Logger) extends Stage1Lib(logger) with Scaffold{
</dependencies>
</project>
val path = jarTarget.toString ++ ( "/" ++ artifactId ++ "-" ++ version ++ ".pom" )
- write.over(Path(path), "<?xml version='1.0' encoding='UTF-8'?>\n" ++ xml.toString)
- new File(path)
+ val file = new File(path)
+ Files.write(file.toPath, ("<?xml version='1.0' encoding='UTF-8'?>\n" ++ xml.toString).getBytes)
+ file
}
def concurrently[T,R]( concurrencyEnabled: Boolean )( items: Seq[T] )( projection: T => R ): Seq[R] = {
@@ -364,7 +368,7 @@ final class Lib(logger: Logger) extends Stage1Lib(logger) with Scaffold{
val httpCon = url.openConnection.asInstanceOf[HttpURLConnection]
httpCon.setDoOutput(true)
httpCon.setRequestMethod("PUT")
- val userPassword = read(Path(sonatypeLogin)).trim
+ val userPassword = new String(readAllBytes(sonatypeLogin.toPath)).trim
val encoding = new sun.misc.BASE64Encoder().encode(userPassword.getBytes)
httpCon.setRequestProperty("Authorization", "Basic " ++ encoding)
httpCon.setRequestProperty("Content-Type", "application/binary")
diff --git a/stage2/NameTransformer.scala b/stage2/NameTransformer.scala
new file mode 100644
index 0000000..33489ca
--- /dev/null
+++ b/stage2/NameTransformer.scala
@@ -0,0 +1,161 @@
+// Adapted from https://github.com/scala/scala/blob/5cb3d4ec14488ce2fc5a1cc8ebdd12845859c57d/src/library/scala/reflect/NameTransformer.scala
+/* __ *\
+** ________ ___ / / ___ Scala API **
+** / __/ __// _ | / / / _ | (c) 2003-2013, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ **
+** /____/\___/_/ |_/____/_/ | | **
+** |/ **
+\* */
+
+package cbt
+
+/** Provides functions to encode and decode Scala symbolic names.
+ * Also provides some constants.
+ */
+object NameTransformer {
+ // XXX Short term: providing a way to alter these without having to recompile
+ // the compiler before recompiling the compiler.
+ val MODULE_SUFFIX_STRING = sys.props.getOrElse("SCALA_MODULE_SUFFIX_STRING", "$")
+ val NAME_JOIN_STRING = sys.props.getOrElse("SCALA_NAME_JOIN_STRING", "$")
+ val MODULE_INSTANCE_NAME = "MODULE$"
+ val LOCAL_SUFFIX_STRING = " "
+ val SETTER_SUFFIX_STRING = "_$eq"
+ val TRAIT_SETTER_SEPARATOR_STRING = "$_setter_$"
+
+ private val nops = 128
+ private val ncodes = 26 * 26
+
+ private class OpCodes(val op: Char, val code: String, val next: OpCodes)
+
+ private val op2code = new Array[String](nops)
+ private val code2op = new Array[OpCodes](ncodes)
+ private def enterOp(op: Char, code: String) = {
+ op2code(op.toInt) = code
+ val c = (code.charAt(1) - 'a') * 26 + code.charAt(2) - 'a'
+ code2op(c.toInt) = new OpCodes(op, code, code2op(c))
+ }
+
+ /* Note: decoding assumes opcodes are only ever lowercase. */
+ enterOp('~', "$tilde")
+ enterOp('=', "$eq")
+ enterOp('<', "$less")
+ enterOp('>', "$greater")
+ enterOp('!', "$bang")
+ enterOp('#', "$hash")
+ enterOp('%', "$percent")
+ enterOp('^', "$up")
+ enterOp('&', "$amp")
+ enterOp('|', "$bar")
+ enterOp('*', "$times")
+ enterOp('/', "$div")
+ enterOp('+', "$plus")
+ enterOp('-', "$minus")
+ enterOp(':', "$colon")
+ enterOp('\\', "$bslash")
+ enterOp('?', "$qmark")
+ enterOp('@', "$at")
+
+ /** Replace operator symbols by corresponding `\$opname`.
+ *
+ * @param name the string to encode
+ * @return the string with all recognized opchars replaced with their encoding
+ */
+ def encode(name: String): String = {
+ var buf: StringBuilder = null
+ val len = name.length()
+ var i = 0
+ while (i < len) {
+ val c = name charAt i
+ if (c < nops && (op2code(c.toInt) ne null)) {
+ if (buf eq null) {
+ buf = new StringBuilder()
+ buf.append(name.substring(0, i))
+ }
+ buf.append(op2code(c.toInt))
+ /* Handle glyphs that are not valid Java/JVM identifiers */
+ }
+ else if (!Character.isJavaIdentifierPart(c)) {
+ if (buf eq null) {
+ buf = new StringBuilder()
+ buf.append(name.substring(0, i))
+ }
+ buf.append("$u%04X".format(c.toInt))
+ }
+ else if (buf ne null) {
+ buf.append(c)
+ }
+ i += 1
+ }
+ if (buf eq null) name else buf.toString()
+ }
+
+ /** Replace `\$opname` by corresponding operator symbol.
+ *
+ * @param name0 the string to decode
+ * @return the string with all recognized operator symbol encodings replaced with their name
+ */
+ def decode(name0: String): String = {
+ //System.out.println("decode: " + name);//DEBUG
+ val name = if (name0.endsWith("<init>")) name0.stripSuffix("<init>") + "this"
+ else name0
+ var buf: StringBuilder = null
+ val len = name.length()
+ var i = 0
+ while (i < len) {
+ var ops: OpCodes = null
+ var unicode = false
+ val c = name charAt i
+ if (c == '$' && i + 2 < len) {
+ val ch1 = name.charAt(i+1)
+ if ('a' <= ch1 && ch1 <= 'z') {
+ val ch2 = name.charAt(i+2)
+ if ('a' <= ch2 && ch2 <= 'z') {
+ ops = code2op((ch1 - 'a') * 26 + ch2 - 'a')
+ while ((ops ne null) && !name.startsWith(ops.code, i)) ops = ops.next
+ if (ops ne null) {
+ if (buf eq null) {
+ buf = new StringBuilder()
+ buf.append(name.substring(0, i))
+ }
+ buf.append(ops.op)
+ i += ops.code.length()
+ }
+ /* Handle the decoding of Unicode glyphs that are
+ * not valid Java/JVM identifiers */
+ } else if ((len - i) >= 6 && // Check that there are enough characters left
+ ch1 == 'u' &&
+ ((Character.isDigit(ch2)) ||
+ ('A' <= ch2 && ch2 <= 'F'))) {
+ /* Skip past "$u", next four should be hexadecimal */
+ val hex = name.substring(i+2, i+6)
+ try {
+ val str = Integer.parseInt(hex, 16).toChar
+ if (buf eq null) {
+ buf = new StringBuilder()
+ buf.append(name.substring(0, i))
+ }
+ buf.append(str)
+ /* 2 for "$u", 4 for hexadecimal number */
+ i += 6
+ unicode = true
+ } catch {
+ case _:NumberFormatException =>
+ /* `hex` did not decode to a hexadecimal number, so
+ * do nothing. */
+ }
+ }
+ }
+ }
+ /* If we didn't see an opcode or encoded Unicode glyph, and the
+ buffer is non-empty, write the current character and advance
+ one */
+ if ((ops eq null) && !unicode) {
+ if (buf ne null)
+ buf.append(c)
+ i += 1
+ }
+ }
+ //System.out.println("= " + (if (buf == null) name else buf.toString()));//DEBUG
+ if (buf eq null) name else buf.toString()
+ }
+}
diff --git a/stage2/PackageBuild.scala b/stage2/PackageBuild.scala
index 2866b7c..79e54a7 100644
--- a/stage2/PackageBuild.scala
+++ b/stage2/PackageBuild.scala
@@ -4,23 +4,23 @@ import scala.collection.immutable.Seq
abstract class PackageBuild(context: Context) extends BasicBuild(context) with ArtifactInfo{
def `package`: Seq[File] = lib.concurrently( enableConcurrency )(
Seq(() => jar, () => docJar, () => srcJar)
- )( _() )
+ )( _() ).flatten
- private object cacheJarBasicBuild extends Cache[File]
- def jar: File = cacheJarBasicBuild{
- lib.jar( artifactId, version, compile, jarTarget )
+ private object cacheJarBasicBuild extends Cache[Option[File]]
+ def jar: Option[File] = cacheJarBasicBuild{
+ compile.flatMap( lib.jar( artifactId, version, _, jarTarget ) )
}
- private object cacheSrcJarBasicBuild extends Cache[File]
- def srcJar: File = cacheSrcJarBasicBuild{
+ private object cacheSrcJarBasicBuild extends Cache[Option[File]]
+ def srcJar: Option[File] = cacheSrcJarBasicBuild{
lib.srcJar( sourceFiles, artifactId, version, scalaTarget )
}
- private object cacheDocBasicBuild extends Cache[File]
- def docJar: File = cacheDocBasicBuild{
- lib.docJar( scalaVersion, sourceFiles, dependencyClasspath, apiTarget, jarTarget, artifactId, version, scalacOptions )
+ private object cacheDocBasicBuild extends Cache[Option[File]]
+ def docJar: Option[File] = cacheDocBasicBuild{
+ lib.docJar( scalaVersion, sourceFiles, dependencyClasspath, apiTarget, jarTarget, artifactId, version, scalacOptions, context.classLoaderCache )
}
- override def jars = jar +: dependencyJars
- override def exportedJars: Seq[File] = Seq(jar)
+ override def jars = jar.toVector ++ dependencyJars
+ override def exportedJars: Seq[File] = jar.toVector
}
diff --git a/stage2/Scaffold.scala b/stage2/Scaffold.scala
index e181ebf..3dcb9ae 100644
--- a/stage2/Scaffold.scala
+++ b/stage2/Scaffold.scala
@@ -1,13 +1,14 @@
package cbt
import java.io._
+import java.nio.file._
import java.net._
-import ammonite.ops.{cwd => _,_}
-
trait Scaffold{
def logger: Logger
private def createFile( projectDirectory: File, fileName: String, code: String ){
- write( Path( projectDirectory.string ++ "/" ++ fileName ), code )
+ val outputFile = projectDirectory ++ ("/" ++ fileName)
+ outputFile.getParentFile.mkdirs
+ Files.write( ( outputFile ).toPath, code.getBytes, StandardOpenOption.CREATE_NEW )
import scala.Console._
println( GREEN ++ "Created " ++ fileName ++ RESET )
}
diff --git a/stage2/Stage2.scala b/stage2/Stage2.scala
index 4145e55..e893a06 100644
--- a/stage2/Stage2.scala
+++ b/stage2/Stage2.scala
@@ -8,26 +8,24 @@ import scala.collection.immutable.Seq
import cbt.paths._
+object Stage2 extends Stage2Base{
+ def run( args: Stage2Args ): Unit = {
+ import args.logger
-object Stage2{
- def main(args: Array[String]): Unit = {
- val init = new Init(args)
- import init._
+ val lib = new Lib(args.logger)
- val lib = new Lib(init.logger)
-
- init.logger.stage2(s"[$now] Stage2 start")
- val loop = argsV.lift(1) == Some("loop")
- val direct = argsV.lift(1) == Some("direct")
+ logger.stage2(s"[$now] Stage2 start")
+ val loop = args.args.lift(0) == Some("loop")
+ val direct = args.args.lift(0) == Some("direct")
val taskIndex = if (loop || direct) {
- 2
- } else {
1
+ } else {
+ 0
}
- val task = argsV.lift( taskIndex )
+ val task = args.args.lift( taskIndex )
- val context = Context( new File(argsV(0)), argsV.drop( taskIndex + 1 ), logger )
+ val context = Context( args.cwd, args.args.drop( taskIndex ), logger, /*args.cbtHasChanged,*/ new ClassLoaderCache(logger) )
val first = lib.loadRoot( context )
val build = first.finalBuild
@@ -47,14 +45,15 @@ object Stage2{
scala.util.control.Breaks.break
case file if triggerFiles.exists(file.toString startsWith _.toString) =>
- val reflectBuild = new lib.ReflectBuild( lib.loadDynamic(context) )
- logger.loop(s"Re-running $task for " ++ reflectBuild.build.projectDirectory.toString)
+ val build = lib.loadDynamic(context)
+ val reflectBuild = new lib.ReflectBuild( build )
+ logger.loop(s"Re-running $task for " ++ build.projectDirectory.toString)
reflectBuild.callNullary(task)
}
} else {
new lib.ReflectBuild(build).callNullary(task)
}
- init.logger.stage2(s"[$now] Stage2 end")
+ logger.stage2(s"[$now] Stage2 end")
}
}
diff --git a/stage2/mixins.scala b/stage2/mixins.scala
index 2b38cdf..c3a57da 100644
--- a/stage2/mixins.scala
+++ b/stage2/mixins.scala
@@ -20,16 +20,13 @@ trait ScalaTest extends Build with Test{
"org.scalatest" %% "scalatest" % scalaTestVersion
) ++ super.dependencies
- // workaround probable ScalaTest bug throwing away the outer classloader. Not caching doesn't nest them.
- override def cacheDependencyClassLoader = false
-
override def run: ExitCode = {
val discoveryPath = compile.toString++"/"
context.logger.lib("discoveryPath: " ++ discoveryPath)
lib.runMain(
"org.scalatest.tools.Runner",
Seq("-R", discoveryPath, "-oF") ++ context.args.drop(1),
- classLoader
+ classLoader(context.classLoaderCache)
)
}
}