From 632f3d0340f66977fd59bb8d0a601a430dd3d0f5 Mon Sep 17 00:00:00 2001 From: Christopher Vogt Date: Sat, 18 Feb 2017 00:59:36 +0000 Subject: 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. --- plugins/essentials/CommandLineOverrides.scala | 16 ++--- stage1/ClassPath.scala | 3 +- stage2/BasicBuild.scala | 11 +-- stage2/Lib.scala | 100 ++++++++++++-------------- stage2/Stage2.scala | 8 +-- stage2/ToolsStage2.scala | 5 +- 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( -- cgit v1.2.3 From 3c135b40c2d2aac017ac12edc6924e3c369296fd Mon Sep 17 00:00:00 2001 From: Christopher Vogt Date: Sat, 18 Feb 2017 01:09:36 +0000 Subject: allow DirectoryDependencies on BuildBuilds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit this fixes a bug where finalBuild would over eagerly go down all the way down to the outermost build instead of stopping at the one requested. Now it checks the new argument and stops there. This is necessary to allow having one build depend on another build in order to embed it in a type-safe way and have access to it’s tasks. --- compatibility/BuildInterface.java | 8 ++++++-- stage2/BasicBuild.scala | 5 +++++ stage2/BuildBuild.scala | 9 ++++++++- stage2/Stage2.scala | 6 +++--- 4 files changed, 22 insertions(+), 6 deletions(-) diff --git a/compatibility/BuildInterface.java b/compatibility/BuildInterface.java index ebcfb99..eb60960 100644 --- a/compatibility/BuildInterface.java +++ b/compatibility/BuildInterface.java @@ -1,11 +1,15 @@ package cbt; import java.io.*; -public abstract class BuildInterface implements Dependency{ - public abstract BuildInterface finalBuild(); // needed to propagage through build builds. Maybe we can get rid of this. +public interface BuildInterface extends Dependency{ + // needed to propagage through build builds. Maybe we can get rid of this. + public default BuildInterface finalBuild(File current){ + return finalBuild(); // legacy forwarder + } public abstract File[] triggerLoopFilesArray(); // needed for watching files across composed builds // deprecated methods, which clients are still allowed to implement, but not required + public abstract BuildInterface finalBuild(); // needed to propagage through build builds. Maybe we can get rid of this. public abstract BuildInterface copy(Context context); public abstract String scalaVersion(); public abstract String[] crossScalaVersionsArray(); diff --git a/stage2/BasicBuild.scala b/stage2/BasicBuild.scala index 4ca39a7..c1a8c4e 100644 --- a/stage2/BasicBuild.scala +++ b/stage2/BasicBuild.scala @@ -253,7 +253,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/Stage2.scala b/stage2/Stage2.scala index 93f0a77..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,7 +39,7 @@ object Stage2 extends Stage2Base{ null ) val first = lib.loadRoot( context ) - val build = first.finalBuild + val build = first.finalBuild( context.cwd ) val res = if (loop) { @@ -57,7 +57,7 @@ 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) lib.callReflective(build, task) } -- cgit v1.2.3 From c9d6afb6f139e7dd4dbe3b1e474f4383570b5ff7 Mon Sep 17 00:00:00 2001 From: Christopher Vogt Date: Sat, 18 Feb 2017 01:21:36 +0000 Subject: support DirectoryDependency on sub builds of a multi project builds --- stage2/BasicBuild.scala | 7 ++++--- stage2/BuildDependency.scala | 31 ++++++++++++++++++++++++++----- 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/stage2/BasicBuild.scala b/stage2/BasicBuild.scala index c1a8c4e..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 @@ -187,7 +188,7 @@ trait BaseBuild extends BuildInterface with DependencyImplementation with Trigge def test: Any = lib.callReflective( - DirectoryDependency(projectDirectory++"/test").build, + DirectoryDependency(projectDirectory++"/test").dependency, Some("run") ) 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() } /* -- cgit v1.2.3 From a849c89dfde99076902c39f14cefb3b24f9bd5d5 Mon Sep 17 00:00:00 2001 From: Christopher Vogt Date: Sat, 18 Feb 2017 01:35:36 +0000 Subject: demonstrate all the different multi-project-build and composition features this should probably be cleaned up and made more explainatory, but seems like at least all the cases are there --- examples/multi-combined-example/build/build.scala | 67 +++++++++++++++------- .../multi-combined-example/build/build/build.scala | 8 +++ .../shared/SomeSharedClass.scala | 2 +- .../sub1/SomeConcreteClass.scala | 2 +- .../sub2/SomeOtherConcreteClass.scala | 7 ++- examples/multi-combined-example/sub3/Main.scala | 6 ++ examples/multi-combined-example/sub4/Class4.scala | 2 + .../multi-combined-example/sub4/build/build.scala | 13 +++++ .../sub4/sub41/sub42/Sub4Example.scala | 2 + examples/multi-combined-example/sub5/Main.scala | 6 ++ .../multi-combined-example/sub6/Sub6Example.scala | 2 + 11 files changed, 92 insertions(+), 25 deletions(-) create mode 100644 examples/multi-combined-example/build/build/build.scala create mode 100644 examples/multi-combined-example/sub3/Main.scala create mode 100644 examples/multi-combined-example/sub4/Class4.scala create mode 100644 examples/multi-combined-example/sub4/build/build.scala create mode 100644 examples/multi-combined-example/sub4/sub41/sub42/Sub4Example.scala create mode 100644 examples/multi-combined-example/sub5/Main.scala create mode 100644 examples/multi-combined-example/sub6/Sub6Example.scala diff --git a/examples/multi-combined-example/build/build.scala b/examples/multi-combined-example/build/build.scala index 7d1ff9f..bdeacc7 100644 --- a/examples/multi-combined-example/build/build.scala +++ b/examples/multi-combined-example/build/build.scala @@ -1,44 +1,69 @@ +package cbt_build.cbt_examples.multi_combined_example import cbt._ -import cbt._ -trait SharedCbtBuild extends BaseBuild{ + +trait ScalaVersion extends BaseBuild{ override def defaultScalaVersion = "2.10.6" } +trait SharedBuild extends ScalaVersion{ + override def dependencies: Seq[Dependency] = Seq( new SharedDependency(context) ) + // Type-safe embedding of other build. Requires dependency in build/build/build.scala + def sub4 = new cbt_build.cbt_examples.multi_combined_example.sub4.Build( + // currently you'll have to provide the correct working directory for that build here + context.copy( workingDirectory = projectDirectory / "sub4" ) + ) + def sub6 = DirectoryDependency(context.workingDirectory / "sub6") +} -class Shared(val context: Context) extends SharedCbtBuild +class SharedDependency(val context: Context) extends ScalaVersion{ + override def projectDirectory = context.workingDirectory / "shared" +} -class Sub(val context:Context) extends SharedCbtBuild{ - override def dependencies = Seq(new Shared( - context.copy( - workingDirectory = projectDirectory ++ "/../shared" - ) - )) +class Sub1(val context: Context) extends SharedBuild{ + override def projectDirectory = context.workingDirectory / "sub1" } -class Build(val context: Context) extends BaseBuild{ +class Sub2(val context: Context) extends SharedBuild{ + override def projectDirectory = context.workingDirectory / "sub2" + override def dependencies = super.dependencies ++ Seq( sub6 ) +} + +class Sub3(val context: Context) extends SharedBuild{ + override def projectDirectory = context.workingDirectory / "sub3" + override def dependencies = Seq( + // Embed another sub build reflectively. Convenient for simple dependencies + DirectoryDependency(context.workingDirectory / "sub4", "sub41", "sub42") + ) +} + +class Sub5(val context: Context) extends SharedBuild{ + override def projectDirectory = context.workingDirectory / "sub5" + override def dependencies = Seq( sub4.sub41.sub42 ) +} + +class Build(val context: Context) extends SharedBuild{ /* Currently each sub build nested into the main build needs to be an instance of a top-level class taking a Context as the sole parameter, similar to the Build class itself. This restriction may be lifted for more flexibility at some point, see https://github.com/cvogt/cbt/issues/306 */ - def sub1 = new Sub( - context.copy( - workingDirectory = projectDirectory ++ "/sub1" - ) - ) - def sub2 = new Sub( - context.copy( - workingDirectory = projectDirectory ++ "/sub2" - ) - ) + def sub1 = new Sub1(context) + def sub2 = new Sub2(context) + def sub3 = new Sub3(context) + def sub5 = new Sub3(context) + + def helloFromSub42 = sub4.sub41.sub42.hello - def sub3 = // DON'T DO THIS, anonymous classes are currently not supported here. + /* + // DON'T DO THIS, anonymous classes are currently not supported here. + def sub3 = new SharedCbtBuild{ def context = Build.this.context.copy( workingDirectory = Build.this.projectDirectory ++ "/sub3" ) } + */ override def dependencies = Seq( sub1, sub2 ) // assembles all projects } diff --git a/examples/multi-combined-example/build/build/build.scala b/examples/multi-combined-example/build/build/build.scala new file mode 100644 index 0000000..b87351d --- /dev/null +++ b/examples/multi-combined-example/build/build/build.scala @@ -0,0 +1,8 @@ +package cbt_build.cbt_examples.multi_combined_example.build +import cbt._ +class Build(val context: Context) extends BuildBuild{ + //println(DirectoryDependency( projectDirectory / ".." / "sub4" / "build" ).dependency.exportedClasspath) + override def dependencies: Seq[cbt.Dependency] = + super.dependencies :+ DirectoryDependency( projectDirectory / ".." / "sub4" / "build" ).dependency + def foo = DirectoryDependency( projectDirectory / ".." / "sub4" / "build" ) +} diff --git a/examples/multi-combined-example/shared/SomeSharedClass.scala b/examples/multi-combined-example/shared/SomeSharedClass.scala index d7d99e4..339d45d 100644 --- a/examples/multi-combined-example/shared/SomeSharedClass.scala +++ b/examples/multi-combined-example/shared/SomeSharedClass.scala @@ -1,4 +1,4 @@ -package cbt.examples.multi_combined +package cbt_examples.multi_combined class SomeSharedClass{ def main(args: Array[String]): Unit = { println(this.getClass.getSimpleName) diff --git a/examples/multi-combined-example/sub1/SomeConcreteClass.scala b/examples/multi-combined-example/sub1/SomeConcreteClass.scala index e55cb12..8039e13 100644 --- a/examples/multi-combined-example/sub1/SomeConcreteClass.scala +++ b/examples/multi-combined-example/sub1/SomeConcreteClass.scala @@ -1,2 +1,2 @@ -package cbt.examples.multi_combined +package cbt_examples.multi_combined object SomeConcreteClass extends SomeSharedClass diff --git a/examples/multi-combined-example/sub2/SomeOtherConcreteClass.scala b/examples/multi-combined-example/sub2/SomeOtherConcreteClass.scala index 399aee6..e339e24 100644 --- a/examples/multi-combined-example/sub2/SomeOtherConcreteClass.scala +++ b/examples/multi-combined-example/sub2/SomeOtherConcreteClass.scala @@ -1,2 +1,5 @@ -package cbt.examples.multi_combined -object SomeOtherConcreteClass extends SomeSharedClass +package cbt_examples.multi_combined +object SomeOtherConcreteClass extends SomeSharedClass{ + new cbt_examples.multi_combined_example.sub6.Sub6Example +} + diff --git a/examples/multi-combined-example/sub3/Main.scala b/examples/multi-combined-example/sub3/Main.scala new file mode 100644 index 0000000..00f8f77 --- /dev/null +++ b/examples/multi-combined-example/sub3/Main.scala @@ -0,0 +1,6 @@ +package cbt_examples.multi_combined_example.sub3 +object Main{ + def main( args: Array[String] ): Unit = { + println( Console.GREEN ++ "Hello " ++ Console.RESET + new cbt_examples.multi_combined_example.sub4.sub41.sub42.Sub4Example ) + } +} diff --git a/examples/multi-combined-example/sub4/Class4.scala b/examples/multi-combined-example/sub4/Class4.scala new file mode 100644 index 0000000..d937122 --- /dev/null +++ b/examples/multi-combined-example/sub4/Class4.scala @@ -0,0 +1,2 @@ +package cbt_examples.multi_combined_example.sub4 +class Sub4Example diff --git a/examples/multi-combined-example/sub4/build/build.scala b/examples/multi-combined-example/sub4/build/build.scala new file mode 100644 index 0000000..df5b689 --- /dev/null +++ b/examples/multi-combined-example/sub4/build/build.scala @@ -0,0 +1,13 @@ +package cbt_build.cbt_examples.multi_combined_example.sub4 +import cbt._ +class Build(val context: Context) extends BaseBuild{ + def sub41 = new Sub41(context) +} +class Sub41(val context: Context) extends BaseBuild{ + override def projectDirectory = context.workingDirectory / "sub41" + def sub42 = new Sub42(context.copy(workingDirectory=projectDirectory)) +} +class Sub42(val context: Context) extends BaseBuild{ + override def projectDirectory = context.workingDirectory / "sub42" + def hello = "Hello from Sub42" +} diff --git a/examples/multi-combined-example/sub4/sub41/sub42/Sub4Example.scala b/examples/multi-combined-example/sub4/sub41/sub42/Sub4Example.scala new file mode 100644 index 0000000..a4ea402 --- /dev/null +++ b/examples/multi-combined-example/sub4/sub41/sub42/Sub4Example.scala @@ -0,0 +1,2 @@ +package cbt_examples.multi_combined_example.sub4.sub41.sub42 +class Sub4Example diff --git a/examples/multi-combined-example/sub5/Main.scala b/examples/multi-combined-example/sub5/Main.scala new file mode 100644 index 0000000..6035cc2 --- /dev/null +++ b/examples/multi-combined-example/sub5/Main.scala @@ -0,0 +1,6 @@ +package cbt_examples.multi_combined_example.sub5 +object Main{ + def main( args: Array[String] ): Unit = { + println( Console.GREEN ++ "Hello " ++ Console.RESET ++ new cbt.examples.multi_combined_example.sub4.sub41.sub42.Sub4Example ) + } +} diff --git a/examples/multi-combined-example/sub6/Sub6Example.scala b/examples/multi-combined-example/sub6/Sub6Example.scala new file mode 100644 index 0000000..bab5993 --- /dev/null +++ b/examples/multi-combined-example/sub6/Sub6Example.scala @@ -0,0 +1,2 @@ +package cbt_examples.multi_combined_example.sub6 +class Sub6Example -- cgit v1.2.3 From fbd06c94329655346d265574a7a28b103abdec93 Mon Sep 17 00:00:00 2001 From: Christopher Vogt Date: Sun, 19 Feb 2017 09:07:06 +0800 Subject: allow git dependencies on sub builds --- stage2/GitDependency.scala | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) 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# + url: String, ref: String, subDirectory: Option[String] = None, // example: git://github.com/cvogt/cbt.git# + 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: _* ) } -- cgit v1.2.3