aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristopher Vogt <oss.nsp@cvogt.org>2017-03-19 19:51:38 -0400
committerChristopher Vogt <oss.nsp@cvogt.org>2017-03-20 02:56:26 -0400
commitd6245c8dc5c7b2f885d538b39f685327da252863 (patch)
tree846bdd92ad022dbe5a7a45e0b9d5e75bbf7779c8
parentca099eba708f3618bed75a5940a5a5ae1d10b684 (diff)
downloadcbt-d6245c8dc5c7b2f885d538b39f685327da252863.tar.gz
cbt-d6245c8dc5c7b2f885d538b39f685327da252863.tar.bz2
cbt-d6245c8dc5c7b2f885d538b39f685327da252863.zip
Unify reflectively loading builds from directories.
THis is mostly cleanup and a little bit feature. Before it was done partially in 3 places, BuildBuild, loadRoot and GitDependency. Now DirectoryDependencies also support referencing sub-builds. Also introduce scalariform for the first few files of cbt's core code :).
-rw-r--r--build/build.scala14
-rw-r--r--build/build/build.scala2
-rw-r--r--compatibility/BuildInterface.java4
-rw-r--r--compatibility/Dependency.java3
-rw-r--r--doc/cbt-developer/version-compatibility.md1
-rw-r--r--examples/multi-combined-example/build/build.scala2
-rw-r--r--plugins/scalariform/Scalariform.scala11
-rw-r--r--stage1/resolver.scala6
-rw-r--r--stage2/BasicBuild.scala15
-rw-r--r--stage2/BuildBuild.scala90
-rw-r--r--stage2/BuildDependency.scala53
-rw-r--r--stage2/DirectoryDependency.scala124
-rw-r--r--stage2/GitDependency.scala24
-rw-r--r--stage2/LazyDependency.scala17
-rw-r--r--stage2/Lib.scala145
-rw-r--r--stage2/Stage2.scala11
-rw-r--r--test/test.scala2
17 files changed, 263 insertions, 261 deletions
diff --git a/build/build.scala b/build/build.scala
index 496acdc..c5ad1b7 100644
--- a/build/build.scala
+++ b/build/build.scala
@@ -1,7 +1,7 @@
import cbt._
import cbt_internal._
-class Build(val context: Context) extends Shared with PublishLocal{
+class Build(val context: Context) extends Shared with Scalariform with PublishLocal{
override def name: String = "cbt"
override def version: String = "0.1"
override def description: String = "Fast, intuitive Build Tool for Scala"
@@ -18,4 +18,16 @@ class Build(val context: Context) extends Shared with PublishLocal{
override def sources = Seq(
"nailgun_launcher", "stage1", "stage2", "compatibility"
).map( projectDirectory / _ ).flatMap( _.listOrFail )
+
+ override def scalariform = super.scalariform.copy(
+ Seq(
+ context.cbtHome / "stage2" / "DirectoryDependency.scala",
+ context.cbtHome / "stage2" / "LazyDependency.scala"
+ )
+ )
+
+ override def compile = {
+ scalariform()
+ super.compile
+ }
}
diff --git a/build/build/build.scala b/build/build/build.scala
index 313b2b5..6752f27 100644
--- a/build/build/build.scala
+++ b/build/build/build.scala
@@ -2,6 +2,6 @@ package cbt_build.cbt.build
import cbt._
class Build(val context: Context) extends CbtInternal{
override def dependencies = (
- super.dependencies :+ cbtInternal.shared
+ super.dependencies :+ cbtInternal.shared :+ plugins.scalariform
)
}
diff --git a/compatibility/BuildInterface.java b/compatibility/BuildInterface.java
index d6d11db..c374d5d 100644
--- a/compatibility/BuildInterface.java
+++ b/compatibility/BuildInterface.java
@@ -12,7 +12,9 @@ public interface BuildInterface extends Dependency{
};
// 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 default BuildInterface finalBuild(){
+ throw new IncompatibleCbtVersionException("You need to define method classLoader.");
+ }; // 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/compatibility/Dependency.java b/compatibility/Dependency.java
index e23faa6..d1d05b9 100644
--- a/compatibility/Dependency.java
+++ b/compatibility/Dependency.java
@@ -9,6 +9,9 @@ public interface Dependency{
public default long lastModified(){
throw new IncompatibleCbtVersionException("You need to define method lastModified.");
};
+ public default ClassLoader classLoader(){
+ throw new IncompatibleCbtVersionException("You need to define method classLoader.");
+ };
// methods that exist for longer which every CBT version in use should have by now, no default values needed
public abstract String show();
diff --git a/doc/cbt-developer/version-compatibility.md b/doc/cbt-developer/version-compatibility.md
index f324c31..d3e2302 100644
--- a/doc/cbt-developer/version-compatibility.md
+++ b/doc/cbt-developer/version-compatibility.md
@@ -22,6 +22,7 @@ minimize the risk.
However there are more things that can break compatibility when changed:
- the format of the `// cbt: ` version string
- the name and format of Build classes (or files) that CBT looks for
+- looking for `build/` directories
- communication between versions via reflection in particular
- how the TrapSecurityManager of each CBT version talks to the
installed TrapSecurityManager via reflection
diff --git a/examples/multi-combined-example/build/build.scala b/examples/multi-combined-example/build/build.scala
index bdeacc7..78e575e 100644
--- a/examples/multi-combined-example/build/build.scala
+++ b/examples/multi-combined-example/build/build.scala
@@ -32,7 +32,7 @@ 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")
+ DirectoryDependency(context.workingDirectory / "sub4", "sub41.sub42")
)
}
diff --git a/plugins/scalariform/Scalariform.scala b/plugins/scalariform/Scalariform.scala
index 8cfc252..83a8bbf 100644
--- a/plugins/scalariform/Scalariform.scala
+++ b/plugins/scalariform/Scalariform.scala
@@ -5,11 +5,11 @@ import java.nio.file.FileSystems
import java.nio.file.Files._
import scalariform.formatter.ScalaFormatter
-import scalariform.formatter.preferences.FormattingPreferences
+import scalariform.formatter.preferences.{ FormattingPreferences, Preserve }
import scalariform.parser.ScalaParserException
trait Scalariform extends BaseBuild {
- def scalariform = Scalariform.apply(lib, sourceFiles.filter(_.string endsWith ".scala"), scalaVersion).config()
+ def scalariform = Scalariform.apply(lib, scalaVersion).config(sourceFiles.filter(_.string endsWith ".scala"))
}
object Scalariform{
@@ -24,11 +24,14 @@ object Scalariform{
.setPreference(SpacesWithinPatternBinders, true)
.setPreference(SpacesAroundMultiImports, true)
.setPreference(DoubleIndentClassDeclaration, false)
+ //.setPreference(NewlineAtEndOfFile, true)
+ .setPreference(DanglingCloseParenthesis, Preserve)
+ .setPreference(PlaceScaladocAsterisksBeneathSecondAsterisk, true)
}
- case class apply( lib: Lib, files: Seq[File], scalaVersion: String ){
+ case class apply( lib: Lib, scalaVersion: String ){
case class config(
- preferences: FormattingPreferences = Scalariform.defaultPreferences
+ files: Seq[File], preferences: FormattingPreferences = Scalariform.defaultPreferences
) extends (() => Seq[File]){
def apply = {
val (successes, errors) = lib.transformFilesOrError( files, in =>
diff --git a/stage1/resolver.scala b/stage1/resolver.scala
index de2f2af..e3500b3 100644
--- a/stage1/resolver.scala
+++ b/stage1/resolver.scala
@@ -440,3 +440,9 @@ object BoundMavenDependency{
}
}
}
+/*
+case class DependencyOr(first: DirectoryDependency, second: JavaDependency) extends ProjectProxy with DirectoryDependencyBase{
+ val isFirst = new File(first.projectDirectory).exists
+ protected val delegate = if(isFirst) first else second
+}
+*/
diff --git a/stage2/BasicBuild.scala b/stage2/BasicBuild.scala
index fcda629..4158040 100644
--- a/stage2/BasicBuild.scala
+++ b/stage2/BasicBuild.scala
@@ -122,11 +122,6 @@ trait BaseBuild extends BuildInterface with DependencyImplementation with SbtDep
scalaVersion: String = scalaMajorVersion, verifyHash: Boolean = true
) = lib.ScalaDependency( groupId, artifactId, version, classifier, scalaVersion, verifyHash )
- final def DirectoryDependency(path: File, pathToNestedBuild: String*) = cbt.DirectoryDependency(
- context.copy( workingDirectory = path ),
- pathToNestedBuild: _*
- )
-
def localJars: Seq[File] =
Seq(projectDirectory ++ "/lib")
.filter(_.exists)
@@ -220,8 +215,7 @@ trait BaseBuild extends BuildInterface with DependencyImplementation with SbtDep
def test: Dependency = {
val testDirectory = projectDirectory / "test"
if( (testDirectory / lib.buildDirectoryName / lib.buildFileName).exists ){
- // FIYME: maybe we can make loadRoot(...).finalBuild an Option some
- DirectoryDependency( testDirectory )
+ DirectoryDependency( testDirectory ).dependency
} else {
new BasicBuild( context.copy(workingDirectory = testDirectory) ){
override def dependencies = Seq(
@@ -292,13 +286,6 @@ trait BaseBuild extends BuildInterface with DependencyImplementation with SbtDep
context.logger.composition("<"*80)
*/
- // ========== 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 ++ ")"
override def toString = show
diff --git a/stage2/BuildBuild.scala b/stage2/BuildBuild.scala
index dea0f12..71a229c 100644
--- a/stage2/BuildBuild.scala
+++ b/stage2/BuildBuild.scala
@@ -5,11 +5,7 @@ import java.io.File
class ConcreteBuildBuild(val context: Context) extends BuildBuild
class plugins(implicit context: Context){
// TODO: move this out of the OO
- private def plugin(dir: String) = cbt.DirectoryDependency(
- context.copy(
- workingDirectory = context.cbtHome / "plugins" / dir
- )
- )
+ private def plugin(dir: String) = cbt.DirectoryDependency(context.cbtHome / "plugins" / dir)
final lazy val googleJavaFormat = plugin( "google-java-format" )
final lazy val proguard = plugin( "proguard" )
final lazy val sbtLayout = plugin( "sbt_layout" )
@@ -24,94 +20,14 @@ class plugins(implicit context: Context){
}
trait BuildBuild extends BaseBuild{
+ override def dependencies = super.dependencies :+ context.cbtDependency
+
object plugins extends plugins
assert(
projectDirectory.getName === lib.buildDirectoryName,
s"You can't extend ${lib.buildBuildClassName} in: " + projectDirectory + "/" + lib.buildDirectoryName
)
-
- protected def managedContext = context.copy(
- workingDirectory = managedBuildDirectory,
- parentBuild=Some(this)
- )
-
- override def dependencies =
- super.dependencies :+ context.cbtDependency
-
- def managedBuildDirectory: java.io.File = lib.realpath( projectDirectory.parent )
- def managedBuild = taskCache[BuildBuild]("managedBuild").memoize{
- val managedBuildFile = projectDirectory++("/"++lib.buildFileName)
- logger.composition("Loading build at " ++ managedBuildDirectory.toString)
- val build = (
- if( !managedBuildFile.exists ){
- throw new Exception(
- s"No file ${lib.buildFileName} (lower case) found in " ++ projectDirectory.getPath
- )
- } else {
- val contents = new String(Files.readAllBytes(managedBuildFile.toPath))
- val cbtUrl = ("cbt:"++GitDependency.GitUrl.regex++"#[a-z0-9A-Z]+").r
- cbtUrl
- .findFirstIn(contents)
- .flatMap{
- url =>
- val Array(base,hash) = url.drop(4).split("#")
- if(context.cbtHome.string.contains(hash))
- None
- else Some{
- // Note: cbt can't use an old version of itself for building,
- // otherwise we'd have to recursively build all versions since
- // the beginning. Instead CBT always needs to build the pure Java
- // Launcher in the checkout with itself and then run it via reflection.
- val build = GitDependency(base, hash, Some("nailgun_launcher")).asInstanceOf[BaseBuild]
- val ctx = managedContext.copy( cbtHome = build.projectDirectory.getParentFile )
- build.classLoader
- .loadClass( "cbt.NailgunLauncher" )
- .getMethod( "getBuild", classOf[AnyRef] )
- .invoke( null, ctx )
- }
- }.getOrElse{
- val buildClasses =
- lib.iterateClasses( compileTarget, classLoader, false )
- .filter(_.getSimpleName == lib.buildClassName)
- .filter(classOf[BaseBuild] isAssignableFrom _)
- if( buildClasses.size == 0 ){
- throw new Exception(
- s"You need to define a class ${lib.buildClassName} extending an appropriate super class in\n"
- + (projectDirectory / lib.buildFileName) ++ "\nbut none found."
- )
- } else if( buildClasses.size > 1 ){
- throw new Exception(
- s"You need to define exactly one class ${lib.buildClassName} extending an appropriate build super class, but multiple found in " + projectDirectory + ":\n" + buildClasses.mkString("\n")
- )
- } else {
- val buildClass = buildClasses.head
- if( !buildClass.getConstructors.exists(_.getParameterTypes.toList == List(classOf[Context])) ){
- throw new Exception(
- s"Expected class ${lib.buildClassName}(val context: Context), but found different constructor in\n"
- + projectDirectory ++ "\n"
- + buildClass ++ "(" ++ buildClass.getConstructors.map(_.getParameterTypes.mkString(", ")).mkString("; ") + ")" )
- }
- buildClass.getConstructors.head.newInstance(managedContext)
- }
- }
- }
- )
- try{
- build.asInstanceOf[BuildInterface]
- } catch {
- case e: ClassCastException if e.getMessage.contains(s"${lib.buildClassName} cannot be cast to cbt.BuildInterface") =>
- throw new Exception(s"Your ${lib.buildClassName} class needs to extend BaseBuild in: "+projectDirectory, e)
- }
- }
-
- @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 )
- }
}
trait CbtInternal extends BuildBuild{
diff --git a/stage2/BuildDependency.scala b/stage2/BuildDependency.scala
deleted file mode 100644
index bd3ed9e..0000000
--- a/stage2/BuildDependency.scala
+++ /dev/null
@@ -1,53 +0,0 @@
-package cbt
-import java.io.File
-/*
-sealed abstract class ProjectProxy extends Ha{
- protected def delegate: ProjectMetaData
- def artifactId: String = delegate.artifactId
- def groupId: String = delegate.groupId
- def version: String = delegate.version
- def exportedClasspath = delegate.exportedClasspath
- def dependencies = Seq(delegate)
-}
-*/
-/** You likely want to use the factory method in the BasicBuild class instead of this. */
-object DirectoryDependency{
- def apply(context: Context, pathToNestedBuild: String*): BuildInterface = {
- val lib: Lib = new Lib(context.logger)
-
- // TODO: move this into finalBuild probably
- // TODO: unify this with lib.callReflective
- def selectNestedBuild( build: BuildInterface, names: Seq[String], previous: Seq[String] ): BuildInterface = {
- names.headOption.map{ name =>
- if( lib.taskNames(build.getClass).contains(name) ){
- val method = build.getClass.getMethod(name)
- val returnType = method.getReturnType
- if( classOf[BuildInterface] isAssignableFrom returnType ){
- selectNestedBuild(
- method.invoke(build).asInstanceOf[BuildInterface],
- names.tail,
- previous :+ name
- )
- } else {
- throw new RuntimeException(
- s"Expected subtype of BuildInterface, found $returnType for " + previous.mkString(".") + " in " + build
- )
- }
- } else {
- throw new RuntimeException( (previous :+ name).mkString(".") + " not found in " + build )
- }
- }.getOrElse( build )
- }
- selectNestedBuild(
- lib.loadRoot( context ).finalBuild(context.workingDirectory),
- pathToNestedBuild,
- Nil
- )
- }
-}
-/*
-case class DependencyOr(first: DirectoryDependency, second: JavaDependency) extends ProjectProxy with DirectoryDependencyBase{
- val isFirst = new File(first.projectDirectory).exists
- protected val delegate = if(isFirst) first else second
-}
-*/
diff --git a/stage2/DirectoryDependency.scala b/stage2/DirectoryDependency.scala
new file mode 100644
index 0000000..cfc0bfd
--- /dev/null
+++ b/stage2/DirectoryDependency.scala
@@ -0,0 +1,124 @@
+package cbt
+import java.io.File
+/** You likely want to use the factory method in the BasicBuild class instead of this. */
+object DirectoryDependency {
+ def apply( path: File, subBuild: String )( implicit context: Context ): LazyDependency =
+ apply( path, Some( subBuild ) )
+ def apply( path: File, subBuild: Option[String] = None )( implicit context: Context ): LazyDependency =
+ apply( context.copy( workingDirectory = path ), subBuild )
+
+ def apply( context: Context, subBuild: Option[String] ): LazyDependency = {
+ val lib: Lib = new Lib( context.logger )
+ val d = context.workingDirectory
+ val relativeBuildFile = lib.buildDirectoryName ++ File.separator ++ lib.buildFileName
+ val buildDirectory = d / lib.buildDirectoryName
+ val buildFile = buildDirectory / lib.buildFileName
+
+ if ( !buildFile.isFile && ( buildDirectory / "Build.scala" ).isFile ) {
+ throw new Exception(
+ s"""expected $relativeBuildFile but found ${lib.buildDirectoryName ++ File.separator ++ "Build.scala"} in $d"""
+ )
+ }
+
+ if ( buildDirectory.exists && buildDirectory.listOrFail.nonEmpty && !buildFile.exists ) {
+ throw new Exception(
+ s"No file ${lib.buildFileName} (lower case) found in " ++ buildDirectory.string
+ )
+ }
+
+ def loadBuild: AnyRef = {
+ val actualBuildFile = (
+ buildFile.getParentFile.getCanonicalFile.getName ++ File.separator ++ buildFile.getCanonicalFile.getName
+ )
+ if ( actualBuildFile =!= relativeBuildFile ) {
+ throw new Exception( s"expected $relativeBuildFile but found $actualBuildFile in " ++ context.workingDirectory.string )
+ }
+
+ if ( buildFile.isFile ) (
+ loadCustomBuildWithDifferentCbtVersion
+ getOrElse loadCustomBuild
+ )
+ else if ( d.getCanonicalFile.getName === lib.buildDirectoryName && ( d / lib.buildFileName ).exists )
+ new cbt.ConcreteBuildBuild( context )
+ else
+ new BasicBuild( context )
+ }
+
+ def loadCustomBuildWithDifferentCbtVersion: Option[AnyRef] = {
+ ( "cbt:" ++ GitDependency.GitUrl.regex ++ "#[a-z0-9A-Z]+" ).r
+ .findFirstIn( buildFile.readAsString )
+ .map( _.drop( 4 ).split( "#" ) )
+ .flatMap {
+ case Array( base, hash ) =>
+ val sameCbtVersion = context.cbtHome.string.contains( hash )
+ if ( sameCbtVersion ) None else Some {
+ // Note: cbt can't use an old version of itself for building,
+ // otherwise we'd have to recursively build all versions since
+ // the beginning. Instead CBT always needs to build the pure Java
+ // Launcher in the checkout with itself and then run it via reflection.
+ val ( checkoutDirectory, dependency ) =
+ GitDependency.withCheckoutDirectory( base, hash, Some( "nailgun_launcher" ) )( context )
+ dependency
+ .dependency
+ .asInstanceOf[BaseBuild] // should work because nailgun_launcher/ has no cbt build of it's own
+ .classLoader
+ .loadClass( "cbt.NailgunLauncher" )
+ .getMethod( "getBuild", classOf[AnyRef] )
+ .invoke( null, context.copy( cbtHome = checkoutDirectory ) )
+ }
+ }
+ }
+
+ def loadCustomBuild: AnyRef = {
+ lib.logger.composition( "Loading build at " ++ buildDirectory.string )
+ val buildBuild = apply( buildDirectory, None )( context ).dependency.asInstanceOf[BuildInterface]
+ import buildBuild._
+ val managedContext = context.copy( parentBuild = Some( buildBuild ) )
+
+ val buildClasses =
+ buildBuild.exportedClasspath.files.flatMap(
+ lib.iterateClasses( _, classLoader, false )
+ .filter( _.getSimpleName === lib.buildClassName )
+ .filter( classOf[BaseBuild] isAssignableFrom _ )
+ )
+
+ if ( buildClasses.size === 0 ) {
+ throw new Exception(
+ s"You need to define a class ${lib.buildClassName} extending an appropriate super class in\n"
+ + buildFile ++ "\nbut none found."
+ )
+ } else if ( buildClasses.size > 1 ) {
+ throw new Exception(
+ s"You need to define exactly one class ${
+ lib.buildClassName
+ } extending an appropriate build super class, but multiple found in "
+ + buildDirectory + ":\n" + buildClasses.mkString( "\n" )
+ )
+ } else {
+ val buildClass = buildClasses.head
+ buildClass.getConstructors.find( _.getParameterTypes.toList === List( classOf[Context] ) ).map {
+ _.newInstance( managedContext ).asInstanceOf[AnyRef]
+ }.getOrElse {
+ throw new Exception(
+ s"Expected class ${lib.buildClassName}(val context: Context), but found different constructor in\n"
+ + buildDirectory ++ "\n"
+ + buildClass ++ "(" ++ buildClass.getConstructors.map( _.getParameterTypes.mkString( ", " ) ).mkString( "; " ) + ")"
+ )
+ }
+ }
+ }
+
+ // wrapping this in lazy so builds are not loaded from disk immediately
+ // this definitely has the advantages that cbt can detect cycles in the
+ // dependencies, but might also positively affect performance
+ new LazyDependency( {
+ val build = lib.getReflective( loadBuild, subBuild )( context )
+ try {
+ build.asInstanceOf[Dependency]
+ } catch {
+ case e: ClassCastException =>
+ throw new RuntimeException( "Your class " ++ lib.buildClassName ++ " needs to extend class BaseBuild in $buildFile", e )
+ }
+ } )( context.logger, context.transientCache, context.classLoaderCache )
+ }
+}
diff --git a/stage2/GitDependency.scala b/stage2/GitDependency.scala
index 20e1d36..8a4a441 100644
--- a/stage2/GitDependency.scala
+++ b/stage2/GitDependency.scala
@@ -11,25 +11,29 @@ object GitDependency{
val GitUrl = "(git@|git://|https://|file:///)([^/:]+)[/:](.+)".r
def apply(
url: String, ref: String, subDirectory: Option[String] = None, // example: git://github.com/cvogt/cbt.git#<some-hash>
- pathToNestedBuild: Seq[String] = Seq()
- )(implicit context: Context ): BuildInterface = {
- def moduleKey = (
+ subBuild: Option[String] = None
+ )(implicit context: Context ): LazyDependency = withCheckoutDirectory(url, ref, subDirectory, subBuild)._2
+ def withCheckoutDirectory(
+ url: String, ref: String, subDirectory: Option[String] = None, // example: git://github.com/cvogt/cbt.git#<some-hash>
+ subBuild: Option[String] = None
+ )(implicit context: Context ): ( File, LazyDependency ) = {
+ lazy val moduleKey = (
this.getClass.getName
++ "(" ++ url ++ subDirectory.map("/" ++ _).getOrElse("") ++ "#" ++ ref
++ ", "
- ++ pathToNestedBuild.mkString(", ")
+ ++ subBuild.mkString(", ")
++ ")"
)
- val taskCache = new PerClassCache(context.transientCache, moduleKey)(context.logger)
-
- val c = taskCache[Dependency]("checkout").memoize{ checkout( url, ref ) }
- DirectoryDependency(
+ val taskCache = new PerClassCache( context.transientCache, moduleKey )( context.logger )
+ val c = taskCache[Dependency]("checkout").memoize{ checkout(url, ref) }
+ val workingDirectory = subDirectory.map(c / _).getOrElse(c)
+ c -> DirectoryDependency(
context.copy(
- workingDirectory = subDirectory.map(c / _).getOrElse(c),
+ workingDirectory = workingDirectory,
loop = false
),
- pathToNestedBuild: _*
+ subBuild
)
}
def checkout(url: String, ref: String)(implicit context: Context): File = {
diff --git a/stage2/LazyDependency.scala b/stage2/LazyDependency.scala
new file mode 100644
index 0000000..afc6263
--- /dev/null
+++ b/stage2/LazyDependency.scala
@@ -0,0 +1,17 @@
+package cbt
+class LazyDependency( _dependency: => Dependency )( implicit logger: Logger, transientCache: java.util.Map[AnyRef, AnyRef], classLoaderCache: ClassLoaderCache ) extends Dependency {
+ lazy val dependency = _dependency
+ def classLoader = dependency.classLoader
+ def dependenciesArray = Array( dependency )
+ def exportedClasspathArray = Array()
+ def lastModified = dependency.lastModified
+ lazy val moduleKey = show
+ def show = s"LazyDependency(${dependency.show})"
+ override def toString = show
+ override def equals( other: Any ) = other match {
+ case d: LazyDependency => d.dependency === dependency
+ case _ => false
+ }
+ def dependencyClasspathArray = dependency.classpath.files.toArray
+ def needsUpdateCompat = false
+}
diff --git a/stage2/Lib.scala b/stage2/Lib.scala
index 04e68f1..4fdcf2d 100644
--- a/stage2/Lib.scala
+++ b/stage2/Lib.scala
@@ -24,30 +24,6 @@ final class Lib(val logger: Logger) extends Stage1Lib(logger){
val buildClassName = "Build"
val buildBuildClassName = "BuildBuild"
- /**
- Loads whatever Build needs to be executed first in order to eventually build the build for the given context.
- This can either the Build itself, of if exists a BuildBuild or a BuildBuild for a BuildBuild and so on.
- */
- def loadRoot(context: Context): BuildInterface = {
- val directory = context.workingDirectory
-
- context.logger.composition( context.logger.showInvocation("Lib.loadRoot",directory) )
-
- val start = lib.findInnerMostModuleDirectory(directory)
-
- val useBasicBuild = directory == start && start.getName != buildDirectoryName
-
- try{
- if(useBasicBuild)
- new BasicBuild( context.copy( workingDirectory = directory ) )
- else
- new cbt.ConcreteBuildBuild( context.copy( workingDirectory = start ) )
- } catch {
- case e:ClassNotFoundException if e.getMessage == buildClassName =>
- throw new Exception(s"no class ${buildClassName} found in " ++ start.string)
- }
- }
-
def scaladoc(
cbtLastModified: Long,
scalaVersion: String,
@@ -118,20 +94,31 @@ final class Lib(val logger: Logger) extends Stage1Lib(logger){
}
def callReflective[T <: AnyRef]( obj: T, code: Option[String], context: Context ): ExitCode = {
- callInternal( obj, code.toSeq.flatMap(_.split("\\.").map( NameTransformer.encode )), Nil, context ).map {
- 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
- }.reduceOption(_ && _).getOrElse( ExitCode.Failure )
+ val result = getReflective( obj, code, f => {
+ def g( a: AnyRef): AnyRef = a match {
+ case bs: Seq[_] if bs.size > 0 && bs.forall(_.isInstanceOf[BaseBuild]) =>
+ bs.map(_.asInstanceOf[BaseBuild]).map(f)
+ case obj: LazyDependency => g(obj.dependency)
+ case obj => f(obj)
+ }
+ g(_)
+ } )( context )
+
+ val applied = result.getClass.getMethods.find(m => m.getName == "apply" && m.getParameterCount == 0).map(
+ _.invoke( result )
+ ).getOrElse( result )
+
+ applied match {
+ case e: ExitCode => e
+ case s: Seq[_] =>
+ val v = render(applied)
+ if(v.nonEmpty) System.out.println(v)
+ s.collect{ case e: ExitCode => e }.reduceOption(_ && _).getOrElse( ExitCode.Success )
+ case other =>
+ val s = render(applied)
+ if(s.nonEmpty) System.out.println(s)
+ ExitCode.Success
+ }
}
private def render( obj: Any ): String = {
@@ -141,55 +128,54 @@ final class Lib(val logger: Logger) extends Stage1Lib(logger){
case url: URL => url.show // to remove credentials
case d: Dependency => lib.usage(d.getClass, d.show())
case c: ClassPath => c.string
- case ExitCode(int) => System.err.println(int); System.exit(int); ???
+ //case e: ExitCode => System.err.println(e.integer); System.exit(e.integer); ???
case s: Seq[_] => s.map(render).mkString("\n")
case s: Set[_] => s.map(render).toSeq.sorted.mkString("\n")
case _ => obj.toString
}
}
- private def callInternal[T <: AnyRef]( obj: T, members: Seq[String], previous: Seq[String], context: Context ): Seq[(Option[Object], Option[ExitCode], Option[String])] = {
- members.headOption.map{ taskName =>
- val name = NameTransformer.decode(taskName)
- logger.lib("Calling task " ++ taskName.toString)
- taskMethods(obj.getClass).get(name).map{ method =>
- Option(trapExitCodeOrValue(method.invoke(obj)).merge /* null in case of Unit */ ).getOrElse(().asInstanceOf[AnyRef]) match {
- case code if code.getClass.getSimpleName == "ExitCode" =>
- // FIXME: ExitCode needs to be part of the compatibility interfaces
- Seq((None, Some(ExitCode(Stage0Lib.get(code,"integer").asInstanceOf[Int])), None))
- case bs: Seq[_] if bs.size > 0 && bs.forall(_.isInstanceOf[BaseBuild]) =>
- bs.flatMap( b => callInternal(b.asInstanceOf[BaseBuild], members.tail, previous :+ taskName, context) )
- case result =>
- callInternal(result, members.tail, previous :+ taskName, context)
- }
- }.getOrElse{
- if( context != null && (context.workingDirectory / name).exists ){
- val newContext = context.copy( workingDirectory = context.workingDirectory / name )
+ def getReflective[T <: AnyRef](
+ obj: T, code: Option[String], callback: (AnyRef => AnyRef) => AnyRef => AnyRef = f => f(_)
+ )( implicit context: Context ) =
+ callInternal( obj, code.toSeq.flatMap(_.split("\\.").map( NameTransformer.encode )), Nil, context, callback )
+
+ private def callInternal[T <: AnyRef](
+ _obj: T, members: Seq[String], previous: Seq[String], context: Context,
+ callback: (AnyRef => AnyRef) => AnyRef => AnyRef
+ ): Object = {
+ callback{ obj =>
+ members.headOption.map{ taskName =>
+ val name = NameTransformer.decode(taskName)
+ logger.lib("Calling task " ++ taskName.toString)
+ taskMethods(obj.getClass).get(name).map{ method =>
callInternal(
- lib.loadRoot(
- newContext
- ).finalBuild,
+ Option(trapExitCodeOrValue(method.invoke(obj)).merge /* null in case of Unit */ ).getOrElse(
+ ().asInstanceOf[AnyRef]
+ ),
members.tail,
- previous,
- newContext
+ previous :+ taskName,
+ context,
+ callback
)
- } else {
- val p = previous.mkString(".")
- val msg = (if(p.nonEmpty) p ++ s" has" else "")
- Seq( ( Some(obj), None, Some( msg ++ s"no method $name\n") ) )
+ }.getOrElse{
+ if( context =!= null && (context.workingDirectory / name).exists ){
+ val newContext = context.copy( workingDirectory = context.workingDirectory / name )
+ callInternal(
+ DirectoryDependency( newContext.cwd )( newContext ).dependency,
+ members.tail,
+ previous :+ taskName,
+ newContext,
+ callback
+ )
+ } else {
+ val p = previous.mkString(".")
+ val msg = (if(p.nonEmpty) p ++ s" has " else " ")
+ throw new RuntimeException( msg ++ lib.red(s"no method `$name` in\n") + render(obj) )
+ }
}
- }
- }.getOrElse{
- Seq((
- Some(
- obj.getClass.getMethods.find(m => m.getName == "apply" && m.getParameterCount == 0).map(
- _.invoke(obj)
- ).getOrElse( obj )
- ),
- None,
- None
- ))
- }
+ }.getOrElse( obj )
+ }(_obj)
}
def consoleOrFail(msg: String) = {
@@ -481,11 +467,6 @@ final class Lib(val logger: Logger) extends Stage1Lib(logger){
url
}
- def findInnerMostModuleDirectory(directory: File): File = {
- val buildDir = realpath( directory ++ ("/" ++ lib.buildDirectoryName) )
- // do not appent buildFileName here, so that we detect empty build folders
- if(buildDir.exists) findInnerMostModuleDirectory(buildDir) else directory
- }
def findOuterMostModuleDirectory(directory: File): File = {
if(
( directory.getParentFile ++ ("/" ++ lib.buildDirectoryName) ).exists
diff --git a/stage2/Stage2.scala b/stage2/Stage2.scala
index eaf776e..9eb47fe 100644
--- a/stage2/Stage2.scala
+++ b/stage2/Stage2.scala
@@ -3,9 +3,7 @@ import java.io._
import java.util.{Set=>_,_}
object Stage2 extends Stage2Base{
- def getBuild(context: Context) = {
- new Lib( context.logger ).loadRoot( context ).finalBuild( context.cwd )
- }
+ def getBuild(context: Context): Dependency = DirectoryDependency( context, None )
def run( args: Stage2Args ): ExitCode = {
import args.logger
@@ -34,9 +32,10 @@ object Stage2 extends Stage2Base{
null,
args.loop
)
- val first = lib.loadRoot( context )
- val build = first.finalBuild( context.cwd )
- val code = lib.callReflective(build, task, context)
+ val code = lib.callReflective(
+ DirectoryDependency( context, None ),
+ task, context
+ )
logger.stage2(s"Stage2 end with exit code "+code.integer)
code
}
diff --git a/test/test.scala b/test/test.scala
index dbade9e..6004311 100644
--- a/test/test.scala
+++ b/test/test.scala
@@ -315,7 +315,7 @@ object Main{
{
val res = runCbt("../examples/dynamic-overrides-example", Seq("eval",""" scalaVersion; 1 + 1 """))
assert(res.exit0)
- assert(res.out == "2\n", res.out ++ "\n\n" ++ res.err)
+ assert(res.out == "2\n", res.out ++ "\n--\n" ++ res.err)
}
{