diff options
author | Christopher Vogt <oss.nsp@cvogt.org> | 2017-03-12 22:00:00 -0400 |
---|---|---|
committer | Christopher Vogt <oss.nsp@cvogt.org> | 2017-03-12 22:34:02 -0400 |
commit | 828adac48d0e08766d192c7ce01021083cfc4d67 (patch) | |
tree | 06e19c3f9117c24e6c6a5e8a95990011a0efe4b3 | |
parent | c14e288996d2b56b6b06a0624f4f2fca315369c7 (diff) | |
download | cbt-828adac48d0e08766d192c7ce01021083cfc4d67.tar.gz cbt-828adac48d0e08766d192c7ce01021083cfc4d67.tar.bz2 cbt-828adac48d0e08766d192c7ce01021083cfc4d67.zip |
fix file watching for real
last file watching update didn’t work well enough. This now
- rips out barbary watch service as it seems buggy crashing the jvm
- make cbt exclusively write files to watch to a file
- uses fswatch instead watching all files in that file
-rw-r--r-- | DEVELOPER_GUIDE.txt | 2 | ||||
-rw-r--r-- | README.md | 8 | ||||
-rw-r--r-- | build/build.scala | 1 | ||||
-rwxr-xr-x | cbt | 72 | ||||
-rw-r--r-- | compatibility/BuildInterface.java | 5 | ||||
-rw-r--r-- | compatibility/Context.java | 6 | ||||
-rw-r--r-- | doc/cbt-developer/version-compatibility.md | 3 | ||||
-rw-r--r-- | nailgun_launcher/NailgunLauncher.java | 43 | ||||
-rw-r--r-- | stage1/ContextImplementation.scala | 2 | ||||
-rw-r--r-- | stage1/Stage1.scala | 22 | ||||
-rw-r--r-- | stage1/Stage1Lib.scala | 8 | ||||
-rw-r--r-- | stage1/cbt.scala | 16 | ||||
-rw-r--r-- | stage1/resolver.scala | 2 | ||||
-rw-r--r-- | stage2/BasicBuild.scala | 29 | ||||
-rw-r--r-- | stage2/BuildBuild.scala | 3 | ||||
-rw-r--r-- | stage2/BuildDependency.scala | 5 | ||||
-rw-r--r-- | stage2/GitDependency.scala | 3 | ||||
-rw-r--r-- | stage2/Lib.scala | 49 | ||||
-rw-r--r-- | stage2/Stage2.scala | 37 | ||||
-rw-r--r-- | test/test.scala | 2 |
20 files changed, 139 insertions, 179 deletions
diff --git a/DEVELOPER_GUIDE.txt b/DEVELOPER_GUIDE.txt index 268852e..1a88253 100644 --- a/DEVELOPER_GUIDE.txt +++ b/DEVELOPER_GUIDE.txt @@ -29,7 +29,7 @@ compatibility/ Java interfaces that all CBT versions are source compatible nailgun_launcher/ Self-contained helper that allows using Nailgun with minimal permanent classpath. (Is this actually needed?) realpath/ Self-contained realpath source code to correctly figure our CBTs home directory. (Open for replacement ideas.) stage1/ CBT's code that only relies only on Scala/Java built-ins. Contains a Maven resolver to download libs for stage2. -stage2/ CBT's code that requires additional libs, e.g. barbary watchservice. +stage2/ CBT's code that requires additional libs, e.g. jgit test/ Unit tests that can serve as example builds sonatype.login Sonatype credentials for deployment. Not in git obviously. @@ -203,7 +203,8 @@ As you can see it prints `asdf`. Adding tasks is that easy. ### Triggering tasks on file-changes -When you call a task, you can prefix it with `loop`. +When you call a task, you can prefix it with `loop`. You need to +have fswatch install (e.g. via `brew install fswatch`). CBT then watches the source files, the build files and even CBT's own source code and re-runs the task when anything changes. If necessary, this forces CBT to re-build itself, the project's dependencies and the project itself. @@ -220,6 +221,11 @@ you managed to change windows back from your editor to the shell. Try changing the build file and see how CBT reacts to it as well. +To also clear the screen on each run use: +``` +$ cbt loop clear run +``` + ### Adding tests The simplest way to add tests is putting a few assertions into the previously diff --git a/build/build.scala b/build/build.scala index ee1acfe..8f094c3 100644 --- a/build/build.scala +++ b/build/build.scala @@ -10,7 +10,6 @@ class Build(val context: Context) extends Shared with PublishLocal{ // FIXME: somehow consolidate this with cbt's own boot-strapping from source. override def dependencies = { super.dependencies ++ Resolver(mavenCentral).bind( - MavenDependency("net.incongru.watchservice","barbary-watchservice","1.0"), MavenDependency("org.eclipse.jgit", "org.eclipse.jgit", "4.2.0.201601211800-r"), ScalaDependency("org.scala-lang.modules","scala-xml","1.0.5") ) @@ -170,6 +170,17 @@ if [ "$1" = "direct" ]; then use_nailgun=1 shift fi +loop=1 +if [ "$1" == "loop" ]; then + loop=0 + shift +fi +clearScreen=1 +if [ "$1" == "clear" ]; then + clearScreen=0 + shift +fi + if [ $nailgun_installed -eq 1 ] || [ "$1" = "publishSigned" ]; then use_nailgun=1 fi @@ -184,7 +195,8 @@ stage1 () { log "Checking for changes in cbt/nailgun_launcher" "$@" NAILGUN_INDICATOR=$NAILGUN$TARGET../classes.last-success changed=1 - for file in "$NAILGUN"/*.java; do + NAILGUN_SOURCES=("$NAILGUN"*.java) + for file in "${NAILGUN_SOURCES[@]}"; do if [ "$file" -nt "$NAILGUN_INDICATOR" ]; then changed=0; fi done exitCode=0 @@ -194,7 +206,8 @@ stage1 () { #rm $NAILGUN$TARGET/cbt/*.class 2>/dev/null # defensive delete of potentially broken class files echo "Compiling cbt/nailgun_launcher" 1>&2 COMPILE_TIME=$(date +%YY%mm%dd%HH%MM.%SS|sed "s/[YmdHMS]//g") - javac -Xlint:deprecation -Xlint:unchecked -d "$NAILGUN$TARGET" "$NAILGUN"*.java + #echo javac -Xlint:deprecation -Xlint:unchecked -d "$NAILGUN$TARGET" "${NAILGUN_SOURCES[@]}" + javac -Xlint:deprecation -Xlint:unchecked -d "$NAILGUN$TARGET" "${NAILGUN_SOURCES[@]}" exitCode=$? if [ $exitCode -eq 0 ]; then touch -t "$COMPILE_TIME" "$NAILGUN_INDICATOR" @@ -214,7 +227,7 @@ stage1 () { log "Running JVM directly" "$@" options=($JAVA_OPTS) # JVM options to improve startup time. See https://github.com/cvogt/cbt/pull/262 - java "${options[@]}" $DEBUG -Xmx6072m -Xss10M -XX:MaxJavaStackTraceDepth=-1 -XX:+TieredCompilation -XX:TieredStopAtLevel=1 -Xverify:none -cp "$NAILGUN$TARGET" cbt.NailgunLauncher "$(time_taken)" "$CWD" "$@" + java "${options[@]}" $DEBUG -Xmx6072m -Xss10M -XX:MaxJavaStackTraceDepth=-1 -XX:+TieredCompilation -XX:TieredStopAtLevel=1 -Xverify:none -cp "$NAILGUN$TARGET" cbt.NailgunLauncher "$(time_taken)" "$CWD" "$loop" "$@" exitCode=$? else log "Running via background process (nailgun)" "$@" @@ -238,7 +251,7 @@ stage1 () { sleep 0.3 done log "Running CBT via Nailgun." "$@" - $NG cbt.NailgunLauncher "$(time_taken)" "$CWD" "$@" + $NG cbt.NailgunLauncher "$(time_taken)" "$CWD" "$loop" "$@" exitCode=$? fi log "Done running CBT." "$@" @@ -246,46 +259,39 @@ stage1 () { } -loop=1 -case "$1" in - "loop") loop=0 -esac -case "$2" in - "loop") loop=0 -esac - -CBT_SIGNALS_LOOPING=253 USER_PRESSED_CTRL_C=130 CBT_LOOP_FILE="$CWD/target/.cbt-loop.tmp" +if [ $loop -eq 0 ]; then + which fswatch >/dev/null 2>/dev/null + export fswatch_installed=$? + if [ ! $fswatch_installed -eq 0 ]; then + echo "please install fswatch to use cbt loop, e.g. via brew install fswatch" + exit 1 + fi +fi while true; do + if [ $clearScreen -eq 0 ]; then + clear + fi + if [ -f "$CBT_LOOP_FILE" ]; then + rm "$CBT_LOOP_FILE" + fi stage1 "$@" if [ ! $loop -eq 0 ] || [ $exitCode -eq $USER_PRESSED_CTRL_C ]; then log "not looping, exiting" "$@" break else - if [ ! $exitCode -eq $CBT_SIGNALS_LOOPING ]; then - log "exitCode $exitCode" "$@" - which fswatch >/dev/null 2>/dev/null - export fswatch_installed=$? - if [ -f "$CBT_LOOP_FILE" ]; then - if [ $fswatch_installed -eq 0 ]; then - # fswatch allows looping over CBT's sources itself - log "fswatch found. looping." "$@" - files=($(cat "$CBT_LOOP_FILE")) - fswatch --one-event "${files[@]}" - else - log "fswatch not installed, stopping cbt" "$@" - break - fi - else - log "no $CBT_LOOP_FILE file, stopping cbt" "$@" - break - fi + files= + if [ -f "$CBT_LOOP_FILE" ]; then + files=($(cat "$CBT_LOOP_FILE")) + #rm "$CBT_LOOP_FILE" fi + echo "" + echo "Watching for file changes... (ctrl+c short press for loop, long press for abort)" + #echo fswatch --one-event "${NAILGUN_SOURCES[@]}" "${files[@]}" + fswatch --one-event "${NAILGUN_SOURCES[@]}" "${files[@]}" fi - - echo "======= Restarting CBT =======" 1>&2 done log "Exiting CBT" "$@" diff --git a/compatibility/BuildInterface.java b/compatibility/BuildInterface.java index eb60960..d6d11db 100644 --- a/compatibility/BuildInterface.java +++ b/compatibility/BuildInterface.java @@ -6,7 +6,10 @@ public interface BuildInterface extends Dependency{ public default BuildInterface finalBuild(File current){ return finalBuild(); // legacy forwarder } - public abstract File[] triggerLoopFilesArray(); // needed for watching files across composed builds + @Deprecated + public default File[] triggerLoopFilesArray(){ + return new File[0]; + }; // 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. diff --git a/compatibility/Context.java b/compatibility/Context.java index 1657ef0..f0fa42c 100644 --- a/compatibility/Context.java +++ b/compatibility/Context.java @@ -21,6 +21,9 @@ public interface Context{ public default File workingDirectory(){ return projectDirectory(); }; + public default boolean loop(){ + return false; + }; // methods that exist for longer which every CBT version in use should have by now, no default values needed public abstract File cwd(); // REPLACE by something that allows to run cbt on some other directly @@ -32,9 +35,6 @@ public interface Context{ public abstract File cbtRootHome(); // REMOVE public abstract File compatibilityTarget(); // maybe replace this with search in the classloader for it? public abstract BuildInterface parentBuildOrNull(); - public default File[] triggerLoopFilesArray(){ - return new File[0]; // REMOVE default value on next compatibility breaking release - } // deprecated methods @java.lang.Deprecated diff --git a/doc/cbt-developer/version-compatibility.md b/doc/cbt-developer/version-compatibility.md index 986bb37..f324c31 100644 --- a/doc/cbt-developer/version-compatibility.md +++ b/doc/cbt-developer/version-compatibility.md @@ -25,6 +25,9 @@ However there are more things that can break compatibility when changed: - communication between versions via reflection in particular - how the TrapSecurityManager of each CBT version talks to the installed TrapSecurityManager via reflection +- communication via the file system + - cache folder location any layout + - .cbt-loop.tmp file ## How to detect accidental breakages? diff --git a/nailgun_launcher/NailgunLauncher.java b/nailgun_launcher/NailgunLauncher.java index 958b052..fe9f27f 100644 --- a/nailgun_launcher/NailgunLauncher.java +++ b/nailgun_launcher/NailgunLauncher.java @@ -6,6 +6,8 @@ import java.security.*; import java.util.*; import static cbt.Stage0Lib.*; import static java.io.File.pathSeparator; +import java.nio.file.*; +import static java.nio.file.Files.write; /** * This launcher allows to start the JVM without loading anything else permanently into its @@ -33,7 +35,9 @@ public class NailgunLauncher{ ((File) get(context, "compatibilityTarget")).toString() + "/", new ClassLoaderCache( (HashMap) get(context, "persistentCache") - ) + ), + (File) get(context, "cwd"), + (Boolean) get(context, "loop") ); return res @@ -97,8 +101,11 @@ public class NailgunLauncher{ String nailgunTarget = CBT_HOME + "/" + NAILGUN + TARGET; long nailgunLauncherLastModified = new File( nailgunTarget + "../classes.last-success" ).lastModified(); + File cwd = new File(args[1]); + boolean loop = args[2].equals("0"); + BuildStage1Result res = buildStage1( - nailgunLauncherLastModified, start, cache, CBT_HOME, compatibilityTarget, classLoaderCache + nailgunLauncherLastModified, start, cache, CBT_HOME, compatibilityTarget, classLoaderCache, cwd, loop ); try{ @@ -106,9 +113,9 @@ public class NailgunLauncher{ .classLoader .loadClass("cbt.Stage1") .getMethod( - "run", String[].class, File.class, File.class, BuildStage1Result.class, Map.class + "run", String[].class, File.class, File.class, boolean.class, BuildStage1Result.class, Map.class ).invoke( - null, (Object) args, new File(cache), new File(CBT_HOME), res, classLoaderCache.hashMap + null, (Object) args, new File(cache), new File(CBT_HOME), loop, res, classLoaderCache.hashMap ); System.exit( exitCode ); @@ -132,7 +139,7 @@ public class NailgunLauncher{ public static BuildStage1Result buildStage1( final long lastModified, final long start, final String cache, final String cbtHome, - final String compatibilityTarget, final ClassLoaderCache classLoaderCache + final String compatibilityTarget, final ClassLoaderCache classLoaderCache, File cwd, boolean loop ) throws Throwable { _assert(TARGET != null, "environment variable TARGET not defined"); String nailgunSources = cbtHome + "/" + NAILGUN; @@ -142,6 +149,10 @@ public class NailgunLauncher{ File compatibilitySources = new File(cbtHome + "/compatibility"); String mavenCache = cache + "maven"; String mavenUrl = "https://repo1.maven.org/maven2"; + File loopFile = new File(cwd + "/target/.cbt-loop.tmp"); + if(loop){ + loopFile.getParentFile().mkdirs(); + } ClassLoader rootClassLoader = new CbtURLClassLoader( new URL[]{}, ClassLoader.getSystemClassLoader().getParent() ); // wrap for caching EarlyDependencies earlyDeps = new EarlyDependencies(mavenCache, mavenUrl, classLoaderCache, rootClassLoader); @@ -166,7 +177,16 @@ public class NailgunLauncher{ } } - compatibilityLastModified = compile( 0L, "", compatibilityTarget, earlyDeps, compatibilitySourceFiles); + if(loop){ + File[] _compatibilitySourceFiles = new File[compatibilitySourceFiles.size()]; + compatibilitySourceFiles.toArray(_compatibilitySourceFiles); + write( + loopFile.toPath(), + (mkString( "\n", _compatibilitySourceFiles ) + "\n").getBytes(), + StandardOpenOption.CREATE + ); + } + compatibilityLastModified = compile( 0L, "", compatibilityTarget, earlyDeps, compatibilitySourceFiles) ; if( !classLoaderCache.containsKey( compatibilityTarget, compatibilityLastModified ) ){ classLoaderCache.put( compatibilityTarget, classLoader(compatibilityTarget, rootClassLoader), compatibilityLastModified ); @@ -197,6 +217,17 @@ public class NailgunLauncher{ lastModified, Math.max( lastModified, compatibilityLastModified ) ); + + if(loop){ + File[] _stage1SourceFiles = new File[stage1SourceFiles.size()]; + stage1SourceFiles.toArray(_stage1SourceFiles); + write( + loopFile.toPath(), + (mkString( "\n", _stage1SourceFiles ) + "\n").getBytes(), + StandardOpenOption.APPEND + ); + } + final long stage1LastModified = compile( stage0LastModified, stage1Classpath, stage1Target, earlyDeps, stage1SourceFiles ); diff --git a/stage1/ContextImplementation.scala b/stage1/ContextImplementation.scala index 90d9d5f..6762cb8 100644 --- a/stage1/ContextImplementation.scala +++ b/stage1/ContextImplementation.scala @@ -16,7 +16,7 @@ class ContextImplementation( override val cbtRootHome: File, override val compatibilityTarget: File, override val parentBuildOrNull: BuildInterface, - override val triggerLoopFilesArray: Array[File] + override val loop: Boolean ) extends Context{ @deprecated("this method is replaced by workingDirectory","") def projectDirectory = workingDirectory diff --git a/stage1/Stage1.scala b/stage1/Stage1.scala index 48d0ae9..e419e67 100644 --- a/stage1/Stage1.scala +++ b/stage1/Stage1.scala @@ -42,7 +42,8 @@ class Stage2Args( val cache: File, val cbtHome: File, val compatibilityTarget: File, - val stage2sourceFiles: Seq[File] + val stage2sourceFiles: Seq[File], + val loop: Boolean )( implicit val transientCache: java.util.Map[AnyRef,AnyRef], val classLoaderCache: ClassLoaderCache, val logger: Logger ){ @@ -60,7 +61,9 @@ object Stage1{ val (_, cbtLastModified, classLoader) = buildStage2( buildStage1, context.cbtHome, - context.cache + context.cache, + context.cwd, + context.loop )(context.transientCache, new ClassLoaderCache( context.persistentCache ), logger) classLoader @@ -75,7 +78,7 @@ object Stage1{ } def buildStage2( - buildStage1: BuildStage1Result, cbtHome: File, cache: File + buildStage1: BuildStage1Result, cbtHome: File, cache: File, cwd: File, loop: Boolean )( implicit transientCache: java.util.Map[AnyRef,AnyRef], classLoaderCache: ClassLoaderCache, logger: Logger ): (Seq[File], Long, ClassLoader) = { @@ -99,6 +102,8 @@ object Stage1{ ) logger.stage1("Compiling stage2 if necessary") + if(loop) + lib.addLoopFiles( cwd, stage2sourceFiles.toSet ) val Some( stage2LastModified ) = compile( buildStage1.stage1LastModified, stage2sourceFiles, stage2Target, stage2StatusFile, @@ -158,6 +163,7 @@ object Stage1{ _args: Array[String], cache: File, cbtHome: File, + loop: Boolean, buildStage1: BuildStage1Result, persistentCache: java.util.Map[AnyRef,AnyRef] ): Int = { @@ -168,17 +174,19 @@ object Stage1{ implicit val transientCache: java.util.Map[AnyRef,AnyRef] = new java.util.HashMap implicit val classLoaderCache = new ClassLoaderCache( persistentCache ) - val (stage2sourceFiles, stage2LastModified, classLoader) = buildStage2( buildStage1, cbtHome, cache ) + val cwd = new File( args.args(0) ) + val (stage2sourceFiles, stage2LastModified, classLoader) = buildStage2( buildStage1, cbtHome, cache, cwd, loop ) val stage2Args = new Stage2Args( - new File( args.args(0) ), - args.args.drop(1).toVector, + cwd, + args.args.drop(2).toVector, // launcher changes cause entire nailgun restart, so no need for them here stage2LastModified = stage2LastModified, cache, cbtHome, new File(buildStage1.compatibilityClasspath), - stage2sourceFiles + stage2sourceFiles, + loop ) logger.stage1(s"Run Stage2") diff --git a/stage1/Stage1Lib.scala b/stage1/Stage1Lib.scala index d99354c..1cda9fd 100644 --- a/stage1/Stage1Lib.scala +++ b/stage1/Stage1Lib.scala @@ -204,7 +204,6 @@ class Stage1Lib( logger: Logger ) extends BaseLib{ } } - def compile( cbtLastModified: Long, sourceFiles: Seq[File], @@ -475,6 +474,13 @@ ${sourceFiles.sorted.mkString(" \\\n")} cache.get( cp, lastModified ) } + def addLoopFiles(cwd: File, files: Set[File]) = { + lib.write( + cwd / "target/.cbt-loop.tmp", + files.map(_ + "\n").mkString, + StandardOpenOption.APPEND + ) + } } import scala.reflect._ diff --git a/stage1/cbt.scala b/stage1/cbt.scala index 05737d0..cb6cb10 100644 --- a/stage1/cbt.scala +++ b/stage1/cbt.scala @@ -84,13 +84,6 @@ object `package`{ } } } - implicit class BuildInterfaceExtensions(build: BuildInterface){ - import build._ - // TODO: if every build has a method triggers a callback if files change - // then we wouldn't need this and could provide this method from a - // plugin rather than hard-coding trigger files stuff in cbt - def triggerLoopFiles: Set[File] = triggerLoopFilesArray.to - } implicit class ArtifactInfoExtensions(subject: ArtifactInfo){ import subject._ def str = s"$groupId:$artifactId:$version" @@ -121,9 +114,6 @@ object `package`{ def scalaVersion = Option(scalaVersionOrNull) def parentBuild = Option(parentBuildOrNull) def cbtLastModified: scala.Long = subject.cbtLastModified - def triggerLoopFiles: Set[File] = triggerLoopFilesArray.toSet[File] - - private[cbt] def loopFile = cwd / "target/.cbt-loop.tmp" def copy( workingDirectory: File = workingDirectory, @@ -133,7 +123,9 @@ object `package`{ scalaVersion: Option[String] = scalaVersion, cbtHome: File = cbtHome, parentBuild: Option[BuildInterface] = None, - triggerLoopFiles: Set[File] = Set() + transientCache: java.util.Map[AnyRef,AnyRef] = transientCache, + persistentCache: java.util.Map[AnyRef,AnyRef] = persistentCache, + loop: Boolean = loop ): Context = new ContextImplementation( workingDirectory, cwd, @@ -149,7 +141,7 @@ object `package`{ cbtRootHome, compatibilityTarget, parentBuild.getOrElse(null), - (triggerLoopFiles ++ triggerLoopFilesArray.toSet[File]).toArray + loop ) } } diff --git a/stage1/resolver.scala b/stage1/resolver.scala index 1304f76..b40fb7b 100644 --- a/stage1/resolver.scala +++ b/stage1/resolver.scala @@ -29,7 +29,6 @@ trait DependencyImplementation extends Dependency{ @deprecated("this method was replaced by dependenciesArray","") def dependencyClasspathArray = dependencyClasspath.files.toArray - /* //private type BuildCache = KeyLockedLazyCache[Dependency, Future[ClassPath]] def exportClasspathConcurrently: ClassPath = { @@ -195,7 +194,6 @@ case class CbtDependencies(mavenCache: File, nailgunTarget: File, stage1Target: stage2Target, stage1Dependency +: MavenResolver(cbtLastModified, mavenCache,mavenCentral).bind( - MavenDependency("net.incongru.watchservice","barbary-watchservice","1.0"), MavenDependency("org.eclipse.jgit", "org.eclipse.jgit", "4.2.0.201601211800-r") ) ) diff --git a/stage2/BasicBuild.scala b/stage2/BasicBuild.scala index 1308155..37b1786 100644 --- a/stage2/BasicBuild.scala +++ b/stage2/BasicBuild.scala @@ -5,7 +5,7 @@ import java.net._ import java.nio.file._ class BasicBuild(final val context: Context) extends BaseBuild -trait BaseBuild extends BuildInterface with DependencyImplementation with TriggerLoop with SbtDependencyDsl{ +trait BaseBuild extends BuildInterface with DependencyImplementation with SbtDependencyDsl{ //* DO NOT OVERRIDE CONTEXT in non-idempotent ways, because .copy and new Build // will create new instances given the context, which means operations in the // overrides will happen multiple times and if they are not idempotent stuff likely breaks @@ -127,12 +127,6 @@ trait BaseBuild extends BuildInterface with DependencyImplementation with Trigge pathToNestedBuild: _* ) - def triggerLoopFiles: Set[File] = ( - context.triggerLoopFiles - ++ sources - ++ transitiveDependencies.collect{ case b: TriggerLoop => b.triggerLoopFiles }.flatten - ) - def localJars: Seq[File] = Seq(projectDirectory ++ "/lib") .filter(_.exists) @@ -164,6 +158,14 @@ trait BaseBuild extends BuildInterface with DependencyImplementation with Trigge final def lastModified: Long = compile.getOrElse(0L) + def triggerLoopFiles: Set[File] = sources.toSet + + if(context.loop){ + taskCache[BasicBuild]( "loop-file-cache" ).memoize{ + lib.addLoopFiles( context.cwd, triggerLoopFiles ) + } + } + def compile: Option[Long] = taskCache[BaseBuild]("_compile").memoize{ lib.compile( Math.max( context.cbtLastModified, context.parentBuild.map(_.lastModified).getOrElse(0L) ), @@ -325,17 +327,4 @@ trait BaseBuild extends BuildInterface with DependencyImplementation with Trigge final def crossScalaVersionsArray = Array(scalaVersion) def publish: Seq[URL] = Seq() - - def loop = { - lib.callReflective(this, context.args.headOption, context.copy(args=context.args.drop(1))) - val files = triggerLoopFiles - lib.watch{ () => - logger.loop("Looping change detection over:\n - "++files.mkString("\n - ")) - files - }() - context.loopFile.getParentFile.mkdirs - lib.write( context.loopFile, files.mkString("\n"), StandardOpenOption.CREATE ) - - ExitCode(253) // signal bash script to restart - } } diff --git a/stage2/BuildBuild.scala b/stage2/BuildBuild.scala index 9811db5..7be4714 100644 --- a/stage2/BuildBuild.scala +++ b/stage2/BuildBuild.scala @@ -37,8 +37,7 @@ trait BuildBuildWithoutEssentials extends BaseBuild{ protected def managedContext = context.copy( workingDirectory = managedBuildDirectory, - parentBuild=Some(this), - triggerLoopFiles = triggerLoopFiles + parentBuild=Some(this) ) override def dependencies = diff --git a/stage2/BuildDependency.scala b/stage2/BuildDependency.scala index 9e35c41..bd3ed9e 100644 --- a/stage2/BuildDependency.scala +++ b/stage2/BuildDependency.scala @@ -10,10 +10,6 @@ sealed abstract class ProjectProxy extends Ha{ def dependencies = Seq(delegate) } */ -trait TriggerLoop extends DependencyImplementation{ - final def triggerLoopFilesArray = triggerLoopFiles.toArray - def triggerLoopFiles: Set[File] -} /** You likely want to use the factory method in the BasicBuild class instead of this. */ object DirectoryDependency{ def apply(context: Context, pathToNestedBuild: String*): BuildInterface = { @@ -52,7 +48,6 @@ object DirectoryDependency{ /* case class DependencyOr(first: DirectoryDependency, second: JavaDependency) extends ProjectProxy with DirectoryDependencyBase{ val isFirst = new File(first.projectDirectory).exists - def triggerLoopFiles = if(isFirst) first.triggerLoopFiles else Set() protected val delegate = if(isFirst) first else second } */ diff --git a/stage2/GitDependency.scala b/stage2/GitDependency.scala index f6812e4..20e1d36 100644 --- a/stage2/GitDependency.scala +++ b/stage2/GitDependency.scala @@ -26,7 +26,8 @@ object GitDependency{ val c = taskCache[Dependency]("checkout").memoize{ checkout( url, ref ) } DirectoryDependency( context.copy( - workingDirectory = subDirectory.map(c / _).getOrElse(c) + workingDirectory = subDirectory.map(c / _).getOrElse(c), + loop = false ), pathToNestedBuild: _* ) diff --git a/stage2/Lib.scala b/stage2/Lib.scala index 5e35ea7..ceea004 100644 --- a/stage2/Lib.scala +++ b/stage2/Lib.scala @@ -151,6 +151,7 @@ final class Lib(val logger: Logger) extends Stage1Lib(logger){ case c: ClassPath => c.string case ExitCode(int) => System.err.println(int); System.exit(int); ??? case s: Seq[_] => s.map(render).mkString("\n") + case s: Set[_] => s.map(render).toSeq.sorted.mkString("\n") case _ => obj.toString } } @@ -478,54 +479,6 @@ final class Lib(val logger: Logger) extends Stage1Lib(logger){ url } - - // code for continuous compile - def watch[T]( files: () => Set[File] )( - action: Seq[File] => Option[T] = (f: Seq[File]) => Some(f) - ): T = { - import com.barbarysoftware.watchservice._ - import scala.collection.JavaConversions._ - Iterator.continually{ - val watcher = WatchService.newWatchService - val realFiles = files().map(realpath) - realFiles.map{ - // WatchService can only watch folders - case file if file.isFile => dirname(file) - case file => file - }.map{ file => - val watchableFile = new WatchableFile(file) - val key = watchableFile.register( - watcher, - StandardWatchEventKind.ENTRY_CREATE, - StandardWatchEventKind.ENTRY_DELETE, - StandardWatchEventKind.ENTRY_MODIFY - ) - } - Option( watcher.take ) -> realFiles - }.collect{ - case (Some(key),f) => key -> f - }.map{ case (key, realFiles) => - logger.loop("Waiting for file changes...") - val changedFiles = key - .pollEvents - .toVector - .filterNot(_.kind == StandardWatchEventKind.OVERFLOW) - .map(_.context.toString) - // make sure we don't react on other files changed - // in the same folder like the files we care about - .filter{ name => realFiles.exists(name startsWith _.toString) } - .map(new File(_)) - - changedFiles.foreach( f => logger.loop( "Changed: " ++ f.toString ) ) - val res = action(changedFiles) - key.reset - res - }.filterNot(_.isEmpty) - .take(1) - .toList - .head.get - } - def findInnerMostModuleDirectory(directory: File): File = { val buildDir = realpath( directory ++ ("/" ++ lib.buildDirectoryName) ) // do not appent buildFileName here, so that we detect empty build folders diff --git a/stage2/Stage2.scala b/stage2/Stage2.scala index 33a67ac..eaf776e 100644 --- a/stage2/Stage2.scala +++ b/stage2/Stage2.scala @@ -32,40 +32,11 @@ object Stage2 extends Stage2Base{ args.cbtHome, args.compatibilityTarget, null, - NailgunLauncher.compatibilitySourceFiles.asScala.toArray[File] - ++ NailgunLauncher.nailgunLauncherSourceFiles.asScala.toArray[File] - ++ NailgunLauncher.stage1SourceFiles.asScala.toArray[File] - ++ args.stage2sourceFiles.toArray[File] + args.loop ) - def loop( code: ExitCode, files: () => Set[File] ): ExitCode = { - code match { - case c@ExitCode(253 | 130) => c // CBT signals loop | user pressed ctrl+C - case c if !task.contains("loop") => c - case c => - // this allows looping over broken builds - lib.watch{ files }() - c - } - } - val code = lib.trapExitCode{ - val first = lib.loadRoot( context ) - val build = first.finalBuild( context.cwd ) - val code = lib.callReflective(build, task, context) - if( !context.loopFile.exists ){ - loop( code, () => build.triggerLoopFiles ) - } - code - } - if( context.loopFile.exists ){ - loop( - code, - () => { - val files = context.loopFile.readAsString.split("\n").map(new File(_)).toSet - logger.loop("Looping change detection over:\n - "++files.mkString("\n - ")) - files - } - ) - } + val first = lib.loadRoot( context ) + val build = first.finalBuild( context.cwd ) + val code = lib.callReflective(build, task, context) logger.stage2(s"Stage2 end with exit code "+code.integer) code } diff --git a/test/test.scala b/test/test.scala index bbecd94..c49d1e5 100644 --- a/test/test.scala +++ b/test/test.scala @@ -144,7 +144,7 @@ object Main{ cbtHome, cbtHome ++ "/compatibilityTarget", null, - Array() + false ) val b = new BasicBuild(noContext){ |