diff options
author | Christopher Vogt <oss.nsp@cvogt.org> | 2017-02-18 00:59:36 +0000 |
---|---|---|
committer | Christopher Vogt <oss.nsp@cvogt.org> | 2017-02-18 00:59:36 +0000 |
commit | 632f3d0340f66977fd59bb8d0a601a430dd3d0f5 (patch) | |
tree | 8d36a536f0a28fb31534e6fab514fcd4d50054cf | |
parent | 86552d373be1b2fbf3e11d1ed223ebc4bdb6b280 (diff) | |
download | cbt-632f3d0340f66977fd59bb8d0a601a430dd3d0f5.tar.gz cbt-632f3d0340f66977fd59bb8d0a601a430dd3d0f5.tar.bz2 cbt-632f3d0340f66977fd59bb8d0a601a430dd3d0f5.zip |
simplify and add features to reflective task lookup code
Code is much simpler now.
Now cbt sub-tasks are separated by . instead of spaces to
unify the syntax with method calls Scala. Also the reflective
code now works not only on builds but any kind of values,
so zero argument members of any types of return values can
simply be called.
This is also a large step towards detangling the reflective
lookup from cbt and turning it into a fully fletched
shell to Scala "native" call solution.
-rw-r--r-- | plugins/essentials/CommandLineOverrides.scala | 16 | ||||
-rw-r--r-- | stage1/ClassPath.scala | 3 | ||||
-rw-r--r-- | stage2/BasicBuild.scala | 11 | ||||
-rw-r--r-- | stage2/Lib.scala | 100 | ||||
-rw-r--r-- | stage2/Stage2.scala | 8 | ||||
-rw-r--r-- | stage2/ToolsStage2.scala | 5 | ||||
-rw-r--r-- | test/test.scala | 10 |
7 files changed, 67 insertions, 86 deletions
diff --git a/plugins/essentials/CommandLineOverrides.scala b/plugins/essentials/CommandLineOverrides.scala index 32b8403..ce68aa9 100644 --- a/plugins/essentials/CommandLineOverrides.scala +++ b/plugins/essentials/CommandLineOverrides.scala @@ -1,25 +1,25 @@ package cbt trait CommandLineOverrides extends DynamicOverrides{ def `with`: Any = { - new lib.ReflectObject( + lib.callReflective( newBuild[DynamicOverrides]( context.copy( args = context.args.drop(2) ) )( s""" ${context.args.lift(0).getOrElse("")} - """ ) - ){ - def usage = "" - }.callNullary(context.args.lift(1) orElse Some("void")) + """ ), + context.args.lift(1) orElse Some("void") + ) } def eval = { - new lib.ReflectObject( + lib.callReflective( newBuild[CommandLineOverrides]( context.copy( args = ( context.args.lift(0).map("println{ "+_+" }") ).toSeq ) - ){""} - ){def usage = ""}.callNullary(Some("with")) + ){""}, + Some("with") + ) } } diff --git a/stage1/ClassPath.scala b/stage1/ClassPath.scala index 6e6f113..d8568c2 100644 --- a/stage1/ClassPath.scala +++ b/stage1/ClassPath.scala @@ -16,7 +16,7 @@ case class ClassPath(files: Seq[File] = Seq()){ nonExisting.isEmpty, "Classpath contains entires that don't exist on disk:\n" ++ nonExisting.mkString("\n") ++ "\nin classpath:\n"++string ) - + def +:(file: File) = ClassPath(file +: files) def :+(file: File) = ClassPath(files :+ file) def ++(other: ClassPath) = ClassPath(files ++ other.files) @@ -24,5 +24,4 @@ case class ClassPath(files: Seq[File] = Seq()){ def strings = files.map{ f => f.string ++ ( if(f.isDirectory) "/" else "" ) }.sorted - def toConsole = string } diff --git a/stage2/BasicBuild.scala b/stage2/BasicBuild.scala index f32d4d9..4ca39a7 100644 --- a/stage2/BasicBuild.scala +++ b/stage2/BasicBuild.scala @@ -186,9 +186,10 @@ trait BaseBuild extends BuildInterface with DependencyImplementation with Trigge def run: ExitCode = run( context.args: _* ) def test: Any = - Some(new lib.ReflectBuild( - DirectoryDependency(projectDirectory++"/test").build - ).callNullary(Some("run"))) + lib.callReflective( + DirectoryDependency(projectDirectory++"/test").build, + Some("run") + ) def t = test def rt = recursiveUnsafe(Some("test")) @@ -221,13 +222,13 @@ trait BaseBuild extends BuildInterface with DependencyImplementation with Trigge recursiveUnsafe(context.args.lift(1)) } - def recursiveUnsafe(taskName: Option[String]): ExitCode = { + def recursiveUnsafe(code: Option[String]): ExitCode = { recursiveSafe{ b => System.err.println(b.show) lib.trapExitCode{ // FIXME: trapExitCode does not seem to work here try{ - new lib.ReflectBuild(b).callNullary(taskName) + lib.callReflective(b,code) ExitCode.Success } catch { case e: Throwable => println(e.getClass); throw e diff --git a/stage2/Lib.scala b/stage2/Lib.scala index b7d1686..9e1f824 100644 --- a/stage2/Lib.scala +++ b/stage2/Lib.scala @@ -108,7 +108,7 @@ final class Lib(val logger: Logger) extends Stage1Lib(logger){ } // task reflection helpers - def tasks(cls:Class[_]): Map[String, Method] = + def taskMethods(cls:Class[_]): Map[String, Method] = Stream .iterate(cls.asInstanceOf[Class[Any]])(_.getSuperclass) .takeWhile(_ != null) @@ -127,7 +127,7 @@ final class Lib(val logger: Logger) extends Stage1Lib(logger){ .map(m => NameTransformer.decode(m.getName) -> m) ).toMap - def taskNames(cls: Class[_]): Seq[String] = tasks(cls).keys.toVector.sorted + def taskNames(cls: Class[_]): Seq[String] = taskMethods(cls).keys.toVector.sorted def usage(buildClass: Class[_], show: String): String = { val baseTasks = Seq( @@ -151,65 +151,53 @@ final class Lib(val logger: Logger) extends Stage1Lib(logger){ ) ++ "\n" } - class ReflectBuild[T:scala.reflect.ClassTag](build: BuildInterface) extends ReflectObject(build){ - def usage = lib.usage(build.getClass, build.show) + def callReflective[T <: AnyRef]( obj: T, code: Option[String] ): ExitCode = { + callInternal( obj, code.toSeq.flatMap(_.split("\\.").map( NameTransformer.encode )), Nil ) match { + case (obj, code, None) => + val s = render(obj) + if(s.nonEmpty) + System.out.println(s) + code getOrElse ExitCode.Success + case (obj, code, Some(msg)) => + if(msg.nonEmpty) + System.err.println(msg) + val s = render(obj) + if(s.nonEmpty) + System.err.println(s) + code getOrElse ExitCode.Failure + } + } + + private def render[T]( obj: T ): String = { + obj match { + case Some(s) => render(s) + case None => "" + case d: Dependency => lib.usage(d.getClass, d.show()) + case c: ClassPath => c.string + case t:ToolsStage2.type => "Available methods: " ++ lib.taskNames(t.getClass).mkString(" ") + case _ => obj.toString + } } - abstract class ReflectObject[T](obj: T){ - def usage: String - def callNullary( taskName: Option[String] ): ExitCode = { + + private def callInternal[T <: AnyRef]( obj: T, members: Seq[String], previous: Seq[String] ): (Option[Object], Option[ExitCode], Option[String]) = { + members.headOption.map{ taskName => logger.lib("Calling task " ++ taskName.toString) - 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( value.getClass.getDeclaredMethod("toConsole") ) match { - case scala.util.Success(toConsole) => - println(toConsole.invoke(value)) - ExitCode.Success - - case scala.util.Failure(e) if Option(e.getMessage).getOrElse("") contains "toConsole" => - value match { - case code if code.getClass.getSimpleName == "ExitCode" => - // FIXME: ExitCode needs to be part of the compatibility interfaces - ExitCode(Stage0Lib.get(code,"integer").asInstanceOf[Int]) - case b: BaseBuild => - val context = b.context.copy(args=b.context.args.drop(1)) - val task = b.context.args.lift(0) - new ReflectBuild( b.copy(context=context) ).callNullary( task ) - case Seq(b: BaseBuild, bs @ _*) if bs.forall(_.isInstanceOf[BaseBuild]) => - (b +: bs) - .map( _.asInstanceOf[BaseBuild] ) - .map{ b => - val task = b.context.args.lift(0) - new ReflectBuild( - b.copy( context = b.context.copy(args=b.context.args.drop(1)) ) - ).callNullary( task ) - } - .head - case other => - println( other.toString ) // no method .toConsole, using to String - ExitCode.Success - } - - case scala.util.Failure(e) => - throw e + taskMethods(obj.getClass).get(taskName).map{ method => + Option(method.invoke(obj) /* null in case of Unit */ ).map{ result => + result match { + case code if code.getClass.getSimpleName == "ExitCode" => + // FIXME: ExitCode needs to be part of the compatibility interfaces + (None, Some(ExitCode(Stage0Lib.get(code,"integer").asInstanceOf[Int])), None) + case Seq(bs @ _*) if bs.forall(_.isInstanceOf[BaseBuild]) => + bs.map( b => callInternal(b.asInstanceOf[BaseBuild], members.tail, previous :+ taskName) ).head + case _ => callInternal(result, members.tail, previous :+ taskName) } - }.getOrElse(ExitCode.Success) + }.getOrElse( (None, None, None) ) }.getOrElse{ - taskName.foreach{ n => - System.err.println(s"Method not found: $n") - System.err.println("") - } - System.err.println(usage) - taskName.map{ _ => - ExitCode.Failure - }.getOrElse( ExitCode.Success ) + ( Some(obj), None, Some("\nMethod not found: " ++ (previous :+ taskName).mkString(".") ++ "\n") ) } + }.getOrElse{ + ( Some(obj), None, None ) } } diff --git a/stage2/Stage2.scala b/stage2/Stage2.scala index 25cd0ae..93f0a77 100644 --- a/stage2/Stage2.scala +++ b/stage2/Stage2.scala @@ -41,10 +41,6 @@ object Stage2 extends Stage2Base{ val first = lib.loadRoot( context ) val build = first.finalBuild - def call(build: BuildInterface): ExitCode = { - new lib.ReflectBuild(build).callNullary(task) - } - val res = if (loop) { // TODO: this should allow looping over task specific files, like test files as well @@ -63,11 +59,11 @@ object Stage2 extends Stage2Base{ case file if triggerFiles.exists(file.toString startsWith _.toString) => val build = lib.loadRoot(context).finalBuild logger.loop(s"Re-running $task for " ++ build.show) - call(build) + lib.callReflective(build, task) } ExitCode.Success } else { - val code = call(build) + val code = lib.callReflective(build, task) logger.stage2(s"Stage2 end") code } diff --git a/stage2/ToolsStage2.scala b/stage2/ToolsStage2.scala index e1c4a8e..c71f2b7 100644 --- a/stage2/ToolsStage2.scala +++ b/stage2/ToolsStage2.scala @@ -5,9 +5,6 @@ object ToolsStage2 extends Stage2Base{ val args = _args.args.dropWhile(Seq("tools","direct") contains _) val lib = new Lib(_args.logger) val toolsTasks = new ToolsTasks(lib, args, _args.cwd, _args.cache, _args.cbtHome, _args.stage2LastModified)(_args.classLoaderCache) - new lib.ReflectObject(toolsTasks){ - def usage: String = "Available methods: " ++ lib.taskNames(toolsTasks.getClass).mkString(" ") - }.callNullary(args.lift(0)) - ExitCode.Success + lib.callReflective(toolsTasks, args.lift(0)) } } diff --git a/test/test.scala b/test/test.scala index d8714c0..5359029 100644 --- a/test/test.scala +++ b/test/test.scala @@ -68,8 +68,8 @@ object Main{ logger.test(res.toString) val debugToken = "usage " ++ path ++ " " assertSuccess(res,debugToken) - assert(res.out == "", debugToken ++ res.toString) - assert(res.err contains usageString, debugToken ++ res.toString) + //assert(res.out == "", res.err.toString) + assert(res.out contains usageString, usageString + " not found in " ++ res.toString) } def compile(path: String)(implicit logger: Logger) = task("compile", path) def task(name: String, path: String)(implicit logger: Logger) = { @@ -85,11 +85,11 @@ object Main{ val debugToken = "\n"++lib.red("Deleting") ++ " " ++ (cbtHome ++("/test/"++path++"/target")).toPath.toAbsolutePath.toString++"\n" val debugToken2 = "\n"++lib.red("Deleting") ++ " " ++ (cbtHome ++("/test/"++path)).toPath.toAbsolutePath.toString++"\n" assertSuccess(res,debugToken) - assert(res.out == "", debugToken ++ " " ++ res.toString) - assert(res.err.contains(debugToken), debugToken ++ " " ++ res.toString) + assert(res.out == "", "should be empty: " + res.out) + assert(res.err.contains(debugToken), debugToken ++ " missing from " ++ res.err.toString) assert( !res.err.contains(debugToken2), - "Tried to delete too much: " ++ debugToken2 ++ " " ++ res.toString + "Tried to delete too much: " ++ debugToken2 ++ " found in " ++ res.err.toString ) res.err.split("\n").filter(_.startsWith(lib.red("Deleting"))).foreach{ line => assert( |