aboutsummaryrefslogtreecommitdiff
path: root/stage1
diff options
context:
space:
mode:
authorChristopher Vogt <oss.nsp@cvogt.org>2017-03-20 22:09:38 -0400
committerChristopher Vogt <oss.nsp@cvogt.org>2017-03-27 19:56:13 -0400
commitbba2abe7ee38b8903822a07578c46466923d13ed (patch)
treea357fb8def6f58a9ea9a37411f3f5640dcb525fe /stage1
parentd2f8cade709b7d55a93e18592b6e38247d648ca9 (diff)
downloadcbt-bba2abe7ee38b8903822a07578c46466923d13ed.tar.gz
cbt-bba2abe7ee38b8903822a07578c46466923d13ed.tar.bz2
cbt-bba2abe7ee38b8903822a07578c46466923d13ed.zip
start modularizing cbt into libraries
this extracts certain parts of cbt into stand-alone libraries, which can be published to maven and used outside of cbt. This also adds scalariform for these parts of the code. This slows down cbt’s own build a lot because of the number of projects involved! So we’ll follow this by a bunch of performance tweak commits.
Diffstat (limited to 'stage1')
-rw-r--r--stage1/Stage1Lib.scala125
-rw-r--r--stage1/cbt.scala62
-rw-r--r--stage1/resolver.scala29
3 files changed, 53 insertions, 163 deletions
diff --git a/stage1/Stage1Lib.scala b/stage1/Stage1Lib.scala
index 565a06c..8aaa6e6 100644
--- a/stage1/Stage1Lib.scala
+++ b/stage1/Stage1Lib.scala
@@ -11,31 +11,11 @@ import java.security._
import java.util.{Set=>_,Map=>_,List=>_,_}
import javax.xml.bind.annotation.adapters.HexBinaryAdapter
-// CLI interop
-case class ExitCode(integer: Int){
- def ||( other: => ExitCode ) = if( this == ExitCode.Success ) this else other
- def &&( other: => ExitCode ) = if( this != ExitCode.Success ) this else other
-}
-object ExitCode{
- val Success = ExitCode(0)
- val Failure = ExitCode(1)
-}
-
-object CatchTrappedExitCode{
- def unapply(e: Throwable): Option[ExitCode] = {
- Option(e) flatMap {
- case i: InvocationTargetException => unapply(i.getTargetException)
- case e if TrapSecurityManager.isTrappedExit(e) => Some( ExitCode(TrapSecurityManager.exitCode(e)) )
- case _ => None
- }
- }
-}
-
-class BaseLib{
- def realpath(name: File) = new File(java.nio.file.Paths.get(name.getAbsolutePath).normalize.toString)
-}
-
-class Stage1Lib( logger: Logger ) extends BaseLib{
+class Stage1Lib( logger: Logger ) extends
+ _root_.cbt.common_1.Module with
+ _root_.cbt.reflect.Module with
+ _root_.cbt.file.Module
+{
lib =>
implicit protected val implicitLogger: Logger = logger
@@ -99,24 +79,28 @@ class Stage1Lib( logger: Logger ) extends BaseLib{
}
}
+ /*
// ========== compilation / execution ==========
// TODO: move classLoader first
- def runMain( cls: String, args: Seq[String], classLoader: ClassLoader, fakeInstance: Boolean = false ): ExitCode = {
+ def runMain( className: String, args: Seq[String], classLoader: ClassLoader ): ExitCode = {
import java.lang.reflect.Modifier
- logger.run(s"Running $cls.main($args) with classLoader: " ++ classLoader.toString)
+ logger.run(s"Running $className.main($args) with classLoader: " ++ classLoader.toString)
trapExitCode{
- val c = classLoader.loadClass(cls)
- val m = c.getMethod( "main", classOf[Array[String]] )
- val instance =
- if(!fakeInstance) null else c.newInstance
- assert(
- fakeInstance || (m.getModifiers & java.lang.reflect.Modifier.STATIC) > 0,
- "Cannot run non-static method " ++ cls+".main"
- )
- m.invoke( instance, args.toArray.asInstanceOf[AnyRef] )
+ /*
+ val cls = classLoader.loadClass(className)
+ discoverCbtMain( cls ) orElse discoverMain( cls ) getOrElse (
+ throw new NoSuchMethodException( "No main method found in " ++ cbt )
+ ).apply( arg.toVector )*/
ExitCode.Success
}
}
+ */
+
+ def discoverCbtMainForced( cls: Class[_] ): cbt.reflect.StaticMethod[Context, ExitCode] =
+ discoverStaticMethodForced[Context, ExitCode]( cls, "cbtMain" )
+
+ def discoverCbtMain( cls: Class[_] ): Option[cbt.reflect.StaticMethod[Context, ExitCode]] =
+ discoverStaticMethod[Context, ExitCode]( cls, "cbtMain" )
/** shows an interactive dialogue in the shell asking the user to pick one of many choices */
def pickOne[T]( msg: String, choices: Seq[T] )( show: T => String ): Option[T] = {
@@ -149,51 +133,10 @@ class Stage1Lib( logger: Logger ) extends BaseLib{
}
/** interactively pick one main class */
- def runClass( mainClasses: Seq[Class[_]] ): Option[Class[_]] = {
+ def pickClass( mainClasses: Seq[Class[_]] ): Option[Class[_]] = {
pickOne( "Which one do you want to run?", mainClasses )( _.toString )
}
- /** Given a directory corresponding to the root package, iterate
- the names of all classes derived from the class files found */
- def iterateClassNames( classesRootDirectory: File ): Seq[String] =
- classesRootDirectory
- .listRecursive
- .filter(_.isFile)
- .map(_.getPath)
- .collect{
- // no $ to avoid inner classes
- case path if !path.contains("$") && path.endsWith(".class") =>
- path.stripSuffix(".class")
- .stripPrefix(classesRootDirectory.getPath)
- .stripPrefix(File.separator) // 1 for the slash
- .replace(File.separator, ".")
- }
-
- /** ignoreMissingClasses allows ignoring other classes root directories which are subdirectories of this one */
- def iterateClasses( classesRootDirectory: File, classLoader: ClassLoader, ignoreMissingClasses: Boolean ) =
- iterateClassNames(classesRootDirectory).map{ name =>
- try{
- classLoader.loadClass(name)
- } catch {
- case e: ClassNotFoundException if ignoreMissingClasses => null
- case e: NoClassDefFoundError if ignoreMissingClasses => null
- }
- }.filterNot(ignoreMissingClasses && _ == null)
-
- def mainClasses( classesRootDirectory: File, classLoader: ClassLoader ): Seq[Class[_]] = {
- val arrayClass = classOf[Array[String]]
- val unitClass = classOf[Unit]
-
- iterateClasses( classesRootDirectory, classLoader, true ).filter( c =>
- !c.isInterface &&
- c.getDeclaredMethods().exists( m =>
- m.getName == "main"
- && m.getParameterTypes.toList == List(arrayClass)
- && m.getReturnType == unitClass
- )
- )
- }
-
implicit class ClassLoaderExtensions(classLoader: ClassLoader){
def canLoad(className: String) = {
try{
@@ -322,8 +265,9 @@ ${sourceFiles.sorted.mkString(" \\\n")}
}
}
}
- def redirectOutToErr[T](code: => T): T = {
- val ( out, err ) = try{
+
+ def getOutErr: (ThreadLocal[PrintStream], ThreadLocal[PrintStream]) =
+ try{
// trying nailgun's System.our/err wrapper
val field = System.out.getClass.getDeclaredField("streams")
assert(System.out.getClass.getName == "com.martiansoftware.nailgun.ThreadLocalPrintStream")
@@ -339,8 +283,8 @@ ${sourceFiles.sorted.mkString(" \\\n")}
field.setAccessible(true)
val outStream = field.get(System.out)
val errStream = field.get(System.err)
- assert(outStream.getClass.getName == "cbt.ThreadLocalOutputStream")
- assert(errStream.getClass.getName == "cbt.ThreadLocalOutputStream")
+ assert(outStream.getClass.getName == "cbt.ThreadLocalOutputStream", outStream.getClass.getName)
+ assert(errStream.getClass.getName == "cbt.ThreadLocalOutputStream", errStream.getClass.getName)
val field2 = outStream.getClass.getDeclaredField("threadLocal")
field2.setAccessible(true)
val out = field2.get(outStream).asInstanceOf[ThreadLocal[PrintStream]]
@@ -348,6 +292,8 @@ ${sourceFiles.sorted.mkString(" \\\n")}
( out, err )
}
+ def redirectOutToErr[T](code: => T): T = {
+ val ( out, err ) = getOutErr
val oldOut: PrintStream = out.get
out.set( err.get: PrintStream )
val res = code
@@ -355,21 +301,6 @@ ${sourceFiles.sorted.mkString(" \\\n")}
res
}
- def trapExitCodeOrValue[T]( result: => T ): Either[ExitCode,T] = {
- val trapExitCodeBefore = TrapSecurityManager.trapExitCode().get
- try{
- TrapSecurityManager.trapExitCode().set(true)
- Right( result )
- } catch {
- case CatchTrappedExitCode(exitCode) =>
- logger.stage1(s"caught exit code $exitCode")
- Left( exitCode )
- } finally {
- TrapSecurityManager.trapExitCode().set(trapExitCodeBefore)
- }
- }
-
- def trapExitCode( code: => ExitCode ): ExitCode = trapExitCodeOrValue(code).merge
def ScalaDependency(
groupId: String, artifactId: String, version: String, classifier: Classifier = Classifier.none,
diff --git a/stage1/cbt.scala b/stage1/cbt.scala
index 8cba9df..64257c2 100644
--- a/stage1/cbt.scala
+++ b/stage1/cbt.scala
@@ -3,13 +3,19 @@ import java.io._
import java.nio.file._
import java.nio.file.Files._
import java.net._
+import java.lang.reflect._
object `package`{
- implicit class TypeInferenceSafeEquals[T](value: T){
- /** if you don't manually upcast, this will catch comparing different types */
- def ===(other: T) = value == other
- def =!=(other: T) = value != other // =!= instead of !==, because it has better precedence
- }
+ implicit class CbtExitCodeOps( val exitCode: ExitCode ) extends AnyVal with common_1.ops.CbtExitCodeOps
+ implicit class TypeInferenceSafeEquals[T]( val value: T ) extends AnyVal with common_1.ops.TypeInferenceSafeEquals[T]
+ implicit class CbtBooleanOps( val condition: Boolean ) extends AnyVal with common_1.ops.CbtBooleanOps
+ implicit class CbtStringOps( val string: String ) extends AnyVal with common_1.ops.CbtStringOps
+
+ implicit class CbtFileOps( val file: File ) extends file.ops.CbtFileOps
+
+ implicit class CbtClassOps( val c: Class[_] ) extends AnyVal with reflect.ops.CbtClassOps
+ implicit class CbtConstructorOps( val c: Constructor[_] ) extends AnyVal with reflect.ops.CbtConstructorOps
+ implicit class CbtMethodOps( val m: Method ) extends AnyVal with reflect.ops.CbtMethodOps
val mavenCentral = new URL("https://repo1.maven.org/maven2")
val jcenter = new URL("https://jcenter.bintray.com")
@@ -18,15 +24,6 @@ object `package`{
val sonatypeReleases = sonatypeBase ++ "releases"
val sonatypeSnapshots = sonatypeBase ++ "snapshots"
- private val lib = new BaseLib
-
- implicit class CbtBooleanExtensions(condition: Boolean){
- def option[T](value: =>T): Option[T] = if(condition) Some(value) else None
- }
- implicit class CbtStringExtensions(string: String){
- def escape = string.replace("\\","\\\\").replace("\"","\\\"")
- def quote = s""""$escape""""
- }
implicit class PathExtensionMethods( path: Path ){
def /(s: String): Path = path.resolve(s)
def ++( s: String ): Path = {
@@ -36,44 +33,7 @@ object `package`{
Paths.get( path.toString ++ s )
}
}
- 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 the trailing "/". Use .stripSuffix("/") if you need to."""
- )
- new File( file.toString ++ s )
- }
- def /(s: String): File = new File( file, s )
- def parent = lib.realpath(file ++ "/..")
- def string = file.toString
- /* recursively deletes folders*/
- def deleteRecursive: Unit = {
- val s = file.string
- // some desperate attempts to keep people from accidentally deleting their hard drive
- assert( file == file.getCanonicalFile, "deleteRecursive requires previous .getCanonicalFile" )
- assert( file.isAbsolute, "deleteRecursive requires absolute path" )
- assert( file.string != "", "deleteRecursive requires non-empty file path" )
- assert( s.split(File.separator.replace("\\","\\\\")).size > 4, "deleteRecursive requires absolute path of at least depth 4" )
- assert( !listRecursive.exists(_.isHidden), "deleteRecursive requires no files to be hidden" )
- assert( listRecursive.forall(_.canWrite), "deleteRecursive requires all files to be writable" )
- if( file.isDirectory ){
- file.listFiles.map(_.deleteRecursive)
- }
- file.delete
- }
-
- def listOrFail: Seq[File] = Option( file.listFiles ).getOrElse( throw new Exception( "no such file: " + file ) ).toVector
- def listRecursive: Seq[File] = {
- file +: (
- if( file.isDirectory ) file.listFiles.flatMap(_.listRecursive).toVector else Seq[File]()
- )
- }
- def lastModifiedRecursive = listRecursive.map(_.lastModified).max
-
- def readAsString = new String( readAllBytes( file.toPath ) )
- def quote = s"new _root_.java.io.File(${string.quote})"
- }
implicit class URLExtensionMethods( url: URL ){
def ++( s: String ): URL = new URL( url.toString ++ s )
def show = "/[^/@]+@".r.replaceFirstIn( url.toString, "/" ) // remove credentials when showing url for security reasons
diff --git a/stage1/resolver.scala b/stage1/resolver.scala
index e3500b3..0f0acaa 100644
--- a/stage1/resolver.scala
+++ b/stage1/resolver.scala
@@ -77,26 +77,25 @@ trait DependencyImplementation extends Dependency{
)
}
*/
-
- def runMain( className: String, args: Seq[String] ) = lib.runMain( className, args, classLoader )
-
def flatClassLoader: Boolean = false
- def mainClasses: Seq[Class[_]] = exportedClasspath.files.flatMap( lib.mainClasses( _, classLoader ) )
-
- def runClass: Option[String] = lib.runClass( mainClasses ).map( _.getName )
+ def runMain( className: String, args: Seq[String] ): ExitCode = lib.trapExitCode{
+ lib.runMain( classLoader.loadClass( className ), args )
+ }
- def run( args: String* ): ExitCode = {
- runClass.map( runMain( _, args ) ).getOrElse{
- // FIXME: this just doing nothing when class is not found has been repeatedly
- // surprising. Let's try to make this more visible than just logging an error.
- // Currently blocked on task `recursive` trying every subbuild and would error
- // for all that don't have a run class. Maybe that's ok actually.
- logger.task( "No main class found for " ++ show )
- ExitCode.Success
- }
+ def runMain( args: Seq[String] ): ExitCode = lib.trapExitCode{
+ mainMethod.getOrElse(
+ throw new RuntimeException( "No main class found in " + this )
+ )( args )
}
+ def mainMethod = lib.pickOne( "Which one do you want to run?", mainMethods )( _.name )
+
+ def classes = exportedClasspath.files.flatMap(
+ lib.iterateClasses( _, classLoader, false )
+ )
+ def mainMethods = classes.flatMap( lib.discoverMain )
+
def classLoader: ClassLoader = {
if( flatClassLoader ){
new java.net.URLClassLoader(classpath.strings.map(f => new URL("file://" ++ f)).toArray)