aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristopher Vogt <oss.nsp@cvogt.org>2017-02-18 00:59:36 +0000
committerChristopher Vogt <oss.nsp@cvogt.org>2017-02-18 00:59:36 +0000
commit632f3d0340f66977fd59bb8d0a601a430dd3d0f5 (patch)
tree8d36a536f0a28fb31534e6fab514fcd4d50054cf
parent86552d373be1b2fbf3e11d1ed223ebc4bdb6b280 (diff)
downloadcbt-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.scala16
-rw-r--r--stage1/ClassPath.scala3
-rw-r--r--stage2/BasicBuild.scala11
-rw-r--r--stage2/Lib.scala100
-rw-r--r--stage2/Stage2.scala8
-rw-r--r--stage2/ToolsStage2.scala5
-rw-r--r--test/test.scala10
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(