diff options
author | Christopher Vogt <oss.nsp@cvogt.org> | 2017-03-20 22:09:38 -0400 |
---|---|---|
committer | Christopher Vogt <oss.nsp@cvogt.org> | 2017-03-27 19:56:13 -0400 |
commit | bba2abe7ee38b8903822a07578c46466923d13ed (patch) | |
tree | a357fb8def6f58a9ea9a37411f3f5640dcb525fe /stage1 | |
parent | d2f8cade709b7d55a93e18592b6e38247d648ca9 (diff) | |
download | cbt-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.scala | 125 | ||||
-rw-r--r-- | stage1/cbt.scala | 62 | ||||
-rw-r--r-- | stage1/resolver.scala | 29 |
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) |