diff options
author | Jan Christopher Vogt <oss.nsp@cvogt.org> | 2016-04-02 16:06:40 -0400 |
---|---|---|
committer | Jan Christopher Vogt <oss.nsp@cvogt.org> | 2016-04-02 16:06:40 -0400 |
commit | 63b54f79c10854e38b2a4a43ee39f508458e280f (patch) | |
tree | 6a5791efedc2d297cfac1ad8bbaac0b090105149 /stage2 | |
parent | 16b02cf34078113c833225297b686752aa26b407 (diff) | |
parent | efe68c7e710aa8c54144715408b7faca36f52c27 (diff) | |
download | cbt-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.scala | 13 | ||||
-rw-r--r-- | stage2/AdminTasks.scala | 109 | ||||
-rw-r--r-- | stage2/BasicBuild.scala | 52 | ||||
-rw-r--r-- | stage2/BuildBuild.scala | 8 | ||||
-rw-r--r-- | stage2/BuildDependency.scala | 2 | ||||
-rw-r--r-- | stage2/GitDependency.scala | 6 | ||||
-rw-r--r-- | stage2/Lib.scala | 234 | ||||
-rw-r--r-- | stage2/NameTransformer.scala | 161 | ||||
-rw-r--r-- | stage2/PackageBuild.scala | 22 | ||||
-rw-r--r-- | stage2/Scaffold.scala | 7 | ||||
-rw-r--r-- | stage2/Stage2.scala | 31 | ||||
-rw-r--r-- | stage2/mixins.scala | 5 |
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) ) } } |