diff options
author | Jan Christopher Vogt <oss.nsp@cvogt.org> | 2017-02-19 11:34:43 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-02-19 11:34:43 +0800 |
commit | cf1cf8678e7ad5366c9ac40451cfa9353bd8b7f3 (patch) | |
tree | 5e169d931f04c588bed876254c7c147825848563 /stage2 | |
parent | 86552d373be1b2fbf3e11d1ed223ebc4bdb6b280 (diff) | |
parent | fbd06c94329655346d265574a7a28b103abdec93 (diff) | |
download | cbt-cf1cf8678e7ad5366c9ac40451cfa9353bd8b7f3.tar.gz cbt-cf1cf8678e7ad5366c9ac40451cfa9353bd8b7f3.tar.bz2 cbt-cf1cf8678e7ad5366c9ac40451cfa9353bd8b7f3.zip |
Merge pull request #348 from cvogt/composing-sub-builds
Composing sub builds
Diffstat (limited to 'stage2')
-rw-r--r-- | stage2/BasicBuild.scala | 21 | ||||
-rw-r--r-- | stage2/BuildBuild.scala | 9 | ||||
-rw-r--r-- | stage2/BuildDependency.scala | 31 | ||||
-rw-r--r-- | stage2/GitDependency.scala | 14 | ||||
-rw-r--r-- | stage2/Lib.scala | 100 | ||||
-rw-r--r-- | stage2/Stage2.scala | 14 | ||||
-rw-r--r-- | stage2/ToolsStage2.scala | 5 |
7 files changed, 109 insertions, 85 deletions
diff --git a/stage2/BasicBuild.scala b/stage2/BasicBuild.scala index f32d4d9..5c49395 100644 --- a/stage2/BasicBuild.scala +++ b/stage2/BasicBuild.scala @@ -106,8 +106,9 @@ trait BaseBuild extends BuildInterface with DependencyImplementation with Trigge scalaVersion: String = scalaMajorVersion ) = lib.ScalaDependency( groupId, artifactId, version, classifier, scalaVersion ) - final def DirectoryDependency(path: File) = cbt.DirectoryDependency( - context.copy( workingDirectory = path, args = Seq() ) + final def DirectoryDependency(path: File, pathToNestedBuild: String*) = cbt.DirectoryDependency( + context.copy( workingDirectory = path ), + pathToNestedBuild: _* ) def triggerLoopFiles: Seq[File] = sources ++ transitiveDependencies.collect{ case b: TriggerLoop => b.triggerLoopFiles }.flatten @@ -186,9 +187,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").dependency, + Some("run") + ) def t = test def rt = recursiveUnsafe(Some("test")) @@ -221,13 +223,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 @@ -252,7 +254,12 @@ trait BaseBuild extends BuildInterface with DependencyImplementation with Trigge */ // ========== cbt internals ========== + @deprecated("use finalbuild(File)","") def finalBuild: BuildInterface = this + override def finalBuild( current: File ): BuildInterface = { + //assert( current.getCanonicalFile == projectDirectory.getCanonicalFile, s"$current == $projectDirectory" ) + this + } override def show = this.getClass.getSimpleName ++ "(" ++ projectDirectory.string ++ ")" // a method that can be called only to trigger any side-effects diff --git a/stage2/BuildBuild.scala b/stage2/BuildBuild.scala index 2eebcbc..9ac631f 100644 --- a/stage2/BuildBuild.scala +++ b/stage2/BuildBuild.scala @@ -1,5 +1,6 @@ package cbt import java.nio.file._ +import java.io.File trait BuildBuild extends BuildBuildWithoutEssentials{ override def dependencies = @@ -97,5 +98,11 @@ trait BuildBuildWithoutEssentials extends BaseBuild{ } } override def triggerLoopFiles = super.triggerLoopFiles ++ managedBuild.triggerLoopFiles - override def finalBuild: BuildInterface = if( projectDirectory == context.cwd ) this else managedBuild.finalBuild + @deprecated("use finalbuild(File)","") + override def finalBuild: BuildInterface = finalBuild( context.cwd ) + override def finalBuild( current: File ): BuildInterface = { + val p = projectDirectory.getCanonicalFile + val c = current.getCanonicalFile + if( c == p ) this else managedBuild.finalBuild( current ) + } } diff --git a/stage2/BuildDependency.scala b/stage2/BuildDependency.scala index 56069c3..0162791 100644 --- a/stage2/BuildDependency.scala +++ b/stage2/BuildDependency.scala @@ -15,7 +15,7 @@ trait TriggerLoop extends DependencyImplementation{ def triggerLoopFiles: Seq[File] } /** You likely want to use the factory method in the BasicBuild class instead of this. */ -final case class DirectoryDependency(context: Context) extends TriggerLoop{ +final case class DirectoryDependency(context: Context, pathToNestedBuild: String*) extends TriggerLoop{ def classLoaderCache = context.classLoaderCache override def toString = show override def show = this.getClass.getSimpleName ++ "(" ++ context.workingDirectory.string ++ ")" @@ -23,12 +23,33 @@ final case class DirectoryDependency(context: Context) extends TriggerLoop{ lazy val logger = context.logger override lazy val lib: Lib = new Lib(logger) def transientCache = context.transientCache - private lazy val root = lib.loadRoot( context.copy(args=Seq()) ) - lazy val build = root.finalBuild + private lazy val root = lib.loadRoot( context ) + lazy val dependency: Dependency = { + def selectNestedBuild( build: Dependency, names: Seq[String], previous: Seq[String] ): Dependency = { + names.headOption.map{ name => + if( lib.taskNames(build.getClass).contains(name) ){ + val method = build.getClass.getMethod(name) + val returnType = method.getReturnType + if( classOf[Dependency] isAssignableFrom returnType ){ + selectNestedBuild( + method.invoke(build).asInstanceOf[Dependency], + names.tail, + previous :+ name + ) + } else { + throw new RuntimeException( s"Expected subtype of Dependency, found $returnType for " + previous.mkString(".") + " in " + show ) + } + } else { + throw new RuntimeException( (previous :+ name).mkString(".") + " not found in " + show ) + } + }.getOrElse( build ) + } + selectNestedBuild( root.finalBuild(context.workingDirectory), pathToNestedBuild, Nil ) + } def exportedClasspath = ClassPath() - def dependencies = Seq(build) + def dependencies = Seq(dependency) def triggerLoopFiles = root.triggerLoopFiles - def lastModified = build.lastModified + def lastModified = dependency.lastModified def targetClasspath = ClassPath() } /* diff --git a/stage2/GitDependency.scala b/stage2/GitDependency.scala index ecd0ee1..f2ac7a6 100644 --- a/stage2/GitDependency.scala +++ b/stage2/GitDependency.scala @@ -11,12 +11,19 @@ object GitDependency{ val GitUrl = "(git:|https:|file:/)//([^/]+)/(.+)".r } case class GitDependency( - url: String, ref: String, subDirectory: Option[String] = None // example: git://github.com/cvogt/cbt.git#<some-hash> + url: String, ref: String, subDirectory: Option[String] = None, // example: git://github.com/cvogt/cbt.git#<some-hash> + pathToNestedBuild: Seq[String] = Seq() )(implicit val logger: Logger, classLoaderCache: ClassLoaderCache, context: Context ) extends DependencyImplementation{ import GitDependency._ override def lib = new Lib(logger) def classLoaderCache = context.classLoaderCache - def moduleKey = this.getClass.getName ++ "(" ++ url ++ subDirectory.map("/" ++ _).getOrElse("") ++ "#" ++ ref ++ ")" + def moduleKey = ( + this.getClass.getName + ++ "(" ++ url ++ subDirectory.map("/" ++ _).getOrElse("") ++ "#" ++ ref + ++ ", " + ++ pathToNestedBuild.mkString(", ") + ++ ")" + ) def transientCache = context.transientCache // TODO: add support for authentication via ssh and/or https // See http://www.codeaffine.com/2014/12/09/jgit-authentication/ @@ -69,7 +76,8 @@ case class GitDependency( DirectoryDependency( context.copy( workingDirectory = checkout ++ subDirectory.map("/" ++ _).getOrElse("") - ) + ), + pathToNestedBuild: _* ) } 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..7ac7a2a 100644 --- a/stage2/Stage2.scala +++ b/stage2/Stage2.scala @@ -4,7 +4,7 @@ import java.util._ object Stage2 extends Stage2Base{ def getBuild(context: Context) = { - new Lib( context.logger ).loadRoot( context ).finalBuild + new Lib( context.logger ).loadRoot( context ).finalBuild( context.cwd ) } def run( args: Stage2Args ): ExitCode = { @@ -39,11 +39,7 @@ object Stage2 extends Stage2Base{ null ) val first = lib.loadRoot( context ) - val build = first.finalBuild - - def call(build: BuildInterface): ExitCode = { - new lib.ReflectBuild(build).callNullary(task) - } + val build = first.finalBuild( context.cwd ) val res = if (loop) { @@ -61,13 +57,13 @@ object Stage2 extends Stage2Base{ scala.util.control.Breaks.break case file if triggerFiles.exists(file.toString startsWith _.toString) => - val build = lib.loadRoot(context).finalBuild + val build = lib.loadRoot(context).finalBuild( context.cwd ) 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)) } } |