Skip to content

Commit 828adac

Browse files
committed
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
1 parent c14e288 commit 828adac

20 files changed

+139
-179
lines changed

DEVELOPER_GUIDE.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ compatibility/ Java interfaces that all CBT versions are source compatible
2929
nailgun_launcher/ Self-contained helper that allows using Nailgun with minimal permanent classpath. (Is this actually needed?)
3030
realpath/ Self-contained realpath source code to correctly figure our CBTs home directory. (Open for replacement ideas.)
3131
stage1/ CBT's code that only relies only on Scala/Java built-ins. Contains a Maven resolver to download libs for stage2.
32-
stage2/ CBT's code that requires additional libs, e.g. barbary watchservice.
32+
stage2/ CBT's code that requires additional libs, e.g. jgit
3333
test/ Unit tests that can serve as example builds
3434
sonatype.login Sonatype credentials for deployment. Not in git obviously.
3535

README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,8 @@ As you can see it prints `asdf`. Adding tasks is that easy.
203203

204204
### Triggering tasks on file-changes
205205

206-
When you call a task, you can prefix it with `loop`.
206+
When you call a task, you can prefix it with `loop`. You need to
207+
have fswatch install (e.g. via `brew install fswatch`).
207208
CBT then watches the source files, the build files and even CBT's own
208209
source code and re-runs the task when anything changes. If necessary,
209210
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.
220221

221222
Try changing the build file and see how CBT reacts to it as well.
222223

224+
To also clear the screen on each run use:
225+
```
226+
$ cbt loop clear run
227+
```
228+
223229
### Adding tests
224230

225231
The simplest way to add tests is putting a few assertions into the previously

build/build.scala

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ class Build(val context: Context) extends Shared with PublishLocal{
1010
// FIXME: somehow consolidate this with cbt's own boot-strapping from source.
1111
override def dependencies = {
1212
super.dependencies ++ Resolver(mavenCentral).bind(
13-
MavenDependency("net.incongru.watchservice","barbary-watchservice","1.0"),
1413
MavenDependency("org.eclipse.jgit", "org.eclipse.jgit", "4.2.0.201601211800-r"),
1514
ScalaDependency("org.scala-lang.modules","scala-xml","1.0.5")
1615
)

cbt

Lines changed: 39 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,17 @@ if [ "$1" = "direct" ]; then
170170
use_nailgun=1
171171
shift
172172
fi
173+
loop=1
174+
if [ "$1" == "loop" ]; then
175+
loop=0
176+
shift
177+
fi
178+
clearScreen=1
179+
if [ "$1" == "clear" ]; then
180+
clearScreen=0
181+
shift
182+
fi
183+
173184
if [ $nailgun_installed -eq 1 ] || [ "$1" = "publishSigned" ]; then
174185
use_nailgun=1
175186
fi
@@ -184,7 +195,8 @@ stage1 () {
184195
log "Checking for changes in cbt/nailgun_launcher" "$@"
185196
NAILGUN_INDICATOR=$NAILGUN$TARGET../classes.last-success
186197
changed=1
187-
for file in "$NAILGUN"/*.java; do
198+
NAILGUN_SOURCES=("$NAILGUN"*.java)
199+
for file in "${NAILGUN_SOURCES[@]}"; do
188200
if [ "$file" -nt "$NAILGUN_INDICATOR" ]; then changed=0; fi
189201
done
190202
exitCode=0
@@ -194,7 +206,8 @@ stage1 () {
194206
#rm $NAILGUN$TARGET/cbt/*.class 2>/dev/null # defensive delete of potentially broken class files
195207
echo "Compiling cbt/nailgun_launcher" 1>&2
196208
COMPILE_TIME=$(date +%YY%mm%dd%HH%MM.%SS|sed "s/[YmdHMS]//g")
197-
javac -Xlint:deprecation -Xlint:unchecked -d "$NAILGUN$TARGET" "$NAILGUN"*.java
209+
#echo javac -Xlint:deprecation -Xlint:unchecked -d "$NAILGUN$TARGET" "${NAILGUN_SOURCES[@]}"
210+
javac -Xlint:deprecation -Xlint:unchecked -d "$NAILGUN$TARGET" "${NAILGUN_SOURCES[@]}"
198211
exitCode=$?
199212
if [ $exitCode -eq 0 ]; then
200213
touch -t "$COMPILE_TIME" "$NAILGUN_INDICATOR"
@@ -214,7 +227,7 @@ stage1 () {
214227
log "Running JVM directly" "$@"
215228
options=($JAVA_OPTS)
216229
# JVM options to improve startup time. See https://github.com/cvogt/cbt/pull/262
217-
java "${options[@]}" $DEBUG -Xmx6072m -Xss10M -XX:MaxJavaStackTraceDepth=-1 -XX:+TieredCompilation -XX:TieredStopAtLevel=1 -Xverify:none -cp "$NAILGUN$TARGET" cbt.NailgunLauncher "$(time_taken)" "$CWD" "$@"
230+
java "${options[@]}" $DEBUG -Xmx6072m -Xss10M -XX:MaxJavaStackTraceDepth=-1 -XX:+TieredCompilation -XX:TieredStopAtLevel=1 -Xverify:none -cp "$NAILGUN$TARGET" cbt.NailgunLauncher "$(time_taken)" "$CWD" "$loop" "$@"
218231
exitCode=$?
219232
else
220233
log "Running via background process (nailgun)" "$@"
@@ -238,54 +251,47 @@ stage1 () {
238251
sleep 0.3
239252
done
240253
log "Running CBT via Nailgun." "$@"
241-
$NG cbt.NailgunLauncher "$(time_taken)" "$CWD" "$@"
254+
$NG cbt.NailgunLauncher "$(time_taken)" "$CWD" "$loop" "$@"
242255
exitCode=$?
243256
fi
244257
log "Done running CBT." "$@"
245258
fi
246259
}
247260

248261

249-
loop=1
250-
case "$1" in
251-
"loop") loop=0
252-
esac
253-
case "$2" in
254-
"loop") loop=0
255-
esac
256-
257-
CBT_SIGNALS_LOOPING=253
258262
USER_PRESSED_CTRL_C=130
259263

260264
CBT_LOOP_FILE="$CWD/target/.cbt-loop.tmp"
265+
if [ $loop -eq 0 ]; then
266+
which fswatch >/dev/null 2>/dev/null
267+
export fswatch_installed=$?
268+
if [ ! $fswatch_installed -eq 0 ]; then
269+
echo "please install fswatch to use cbt loop, e.g. via brew install fswatch"
270+
exit 1
271+
fi
272+
fi
261273
while true; do
274+
if [ $clearScreen -eq 0 ]; then
275+
clear
276+
fi
277+
if [ -f "$CBT_LOOP_FILE" ]; then
278+
rm "$CBT_LOOP_FILE"
279+
fi
262280
stage1 "$@"
263281
if [ ! $loop -eq 0 ] || [ $exitCode -eq $USER_PRESSED_CTRL_C ]; then
264282
log "not looping, exiting" "$@"
265283
break
266284
else
267-
if [ ! $exitCode -eq $CBT_SIGNALS_LOOPING ]; then
268-
log "exitCode $exitCode" "$@"
269-
which fswatch >/dev/null 2>/dev/null
270-
export fswatch_installed=$?
271-
if [ -f "$CBT_LOOP_FILE" ]; then
272-
if [ $fswatch_installed -eq 0 ]; then
273-
# fswatch allows looping over CBT's sources itself
274-
log "fswatch found. looping." "$@"
275-
files=($(cat "$CBT_LOOP_FILE"))
276-
fswatch --one-event "${files[@]}"
277-
else
278-
log "fswatch not installed, stopping cbt" "$@"
279-
break
280-
fi
281-
else
282-
log "no $CBT_LOOP_FILE file, stopping cbt" "$@"
283-
break
284-
fi
285+
files=
286+
if [ -f "$CBT_LOOP_FILE" ]; then
287+
files=($(cat "$CBT_LOOP_FILE"))
288+
#rm "$CBT_LOOP_FILE"
285289
fi
290+
echo ""
291+
echo "Watching for file changes... (ctrl+c short press for loop, long press for abort)"
292+
#echo fswatch --one-event "${NAILGUN_SOURCES[@]}" "${files[@]}"
293+
fswatch --one-event "${NAILGUN_SOURCES[@]}" "${files[@]}"
286294
fi
287-
288-
echo "======= Restarting CBT =======" 1>&2
289295
done
290296

291297
log "Exiting CBT" "$@"

compatibility/BuildInterface.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ public interface BuildInterface extends Dependency{
66
public default BuildInterface finalBuild(File current){
77
return finalBuild(); // legacy forwarder
88
}
9-
public abstract File[] triggerLoopFilesArray(); // needed for watching files across composed builds
9+
@Deprecated
10+
public default File[] triggerLoopFilesArray(){
11+
return new File[0];
12+
};
1013

1114
// deprecated methods, which clients are still allowed to implement, but not required
1215
public abstract BuildInterface finalBuild(); // needed to propagage through build builds. Maybe we can get rid of this.

compatibility/Context.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ public default long start(){
2121
public default File workingDirectory(){
2222
return projectDirectory();
2323
};
24+
public default boolean loop(){
25+
return false;
26+
};
2427

2528
// methods that exist for longer which every CBT version in use should have by now, no default values needed
2629
public abstract File cwd(); // REPLACE by something that allows to run cbt on some other directly
@@ -32,9 +35,6 @@ public default File workingDirectory(){
3235
public abstract File cbtRootHome(); // REMOVE
3336
public abstract File compatibilityTarget(); // maybe replace this with search in the classloader for it?
3437
public abstract BuildInterface parentBuildOrNull();
35-
public default File[] triggerLoopFilesArray(){
36-
return new File[0]; // REMOVE default value on next compatibility breaking release
37-
}
3838

3939
// deprecated methods
4040
@java.lang.Deprecated

doc/cbt-developer/version-compatibility.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ However there are more things that can break compatibility when changed:
2525
- communication between versions via reflection in particular
2626
- how the TrapSecurityManager of each CBT version talks to the
2727
installed TrapSecurityManager via reflection
28+
- communication via the file system
29+
- cache folder location any layout
30+
- .cbt-loop.tmp file
2831

2932
## How to detect accidental breakages?
3033

nailgun_launcher/NailgunLauncher.java

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
import java.util.*;
77
import static cbt.Stage0Lib.*;
88
import static java.io.File.pathSeparator;
9+
import java.nio.file.*;
10+
import static java.nio.file.Files.write;
911

1012
/**
1113
* This launcher allows to start the JVM without loading anything else permanently into its
@@ -33,7 +35,9 @@ public static Object getBuild( Object context ) throws Throwable{
3335
((File) get(context, "compatibilityTarget")).toString() + "/",
3436
new ClassLoaderCache(
3537
(HashMap) get(context, "persistentCache")
36-
)
38+
),
39+
(File) get(context, "cwd"),
40+
(Boolean) get(context, "loop")
3741
);
3842
return
3943
res
@@ -97,18 +101,21 @@ public static void main( String[] args ) throws Throwable {
97101
String nailgunTarget = CBT_HOME + "/" + NAILGUN + TARGET;
98102
long nailgunLauncherLastModified = new File( nailgunTarget + "../classes.last-success" ).lastModified();
99103

104+
File cwd = new File(args[1]);
105+
boolean loop = args[2].equals("0");
106+
100107
BuildStage1Result res = buildStage1(
101-
nailgunLauncherLastModified, start, cache, CBT_HOME, compatibilityTarget, classLoaderCache
108+
nailgunLauncherLastModified, start, cache, CBT_HOME, compatibilityTarget, classLoaderCache, cwd, loop
102109
);
103110

104111
try{
105112
int exitCode = (int) res
106113
.classLoader
107114
.loadClass("cbt.Stage1")
108115
.getMethod(
109-
"run", String[].class, File.class, File.class, BuildStage1Result.class, Map.class
116+
"run", String[].class, File.class, File.class, boolean.class, BuildStage1Result.class, Map.class
110117
).invoke(
111-
null, (Object) args, new File(cache), new File(CBT_HOME), res, classLoaderCache.hashMap
118+
null, (Object) args, new File(cache), new File(CBT_HOME), loop, res, classLoaderCache.hashMap
112119
);
113120

114121
System.exit( exitCode );
@@ -132,7 +139,7 @@ public static Throwable unwrapInvocationTargetException(Throwable e){
132139

133140
public static BuildStage1Result buildStage1(
134141
final long lastModified, final long start, final String cache, final String cbtHome,
135-
final String compatibilityTarget, final ClassLoaderCache classLoaderCache
142+
final String compatibilityTarget, final ClassLoaderCache classLoaderCache, File cwd, boolean loop
136143
) throws Throwable {
137144
_assert(TARGET != null, "environment variable TARGET not defined");
138145
String nailgunSources = cbtHome + "/" + NAILGUN;
@@ -142,6 +149,10 @@ public static BuildStage1Result buildStage1(
142149
File compatibilitySources = new File(cbtHome + "/compatibility");
143150
String mavenCache = cache + "maven";
144151
String mavenUrl = "https://repo1.maven.org/maven2";
152+
File loopFile = new File(cwd + "/target/.cbt-loop.tmp");
153+
if(loop){
154+
loopFile.getParentFile().mkdirs();
155+
}
145156

146157
ClassLoader rootClassLoader = new CbtURLClassLoader( new URL[]{}, ClassLoader.getSystemClassLoader().getParent() ); // wrap for caching
147158
EarlyDependencies earlyDeps = new EarlyDependencies(mavenCache, mavenUrl, classLoaderCache, rootClassLoader);
@@ -166,7 +177,16 @@ public static BuildStage1Result buildStage1(
166177
}
167178
}
168179

169-
compatibilityLastModified = compile( 0L, "", compatibilityTarget, earlyDeps, compatibilitySourceFiles);
180+
if(loop){
181+
File[] _compatibilitySourceFiles = new File[compatibilitySourceFiles.size()];
182+
compatibilitySourceFiles.toArray(_compatibilitySourceFiles);
183+
write(
184+
loopFile.toPath(),
185+
(mkString( "\n", _compatibilitySourceFiles ) + "\n").getBytes(),
186+
StandardOpenOption.CREATE
187+
);
188+
}
189+
compatibilityLastModified = compile( 0L, "", compatibilityTarget, earlyDeps, compatibilitySourceFiles) ;
170190

171191
if( !classLoaderCache.containsKey( compatibilityTarget, compatibilityLastModified ) ){
172192
classLoaderCache.put( compatibilityTarget, classLoader(compatibilityTarget, rootClassLoader), compatibilityLastModified );
@@ -197,6 +217,17 @@ public static BuildStage1Result buildStage1(
197217
lastModified,
198218
Math.max( lastModified, compatibilityLastModified )
199219
);
220+
221+
if(loop){
222+
File[] _stage1SourceFiles = new File[stage1SourceFiles.size()];
223+
stage1SourceFiles.toArray(_stage1SourceFiles);
224+
write(
225+
loopFile.toPath(),
226+
(mkString( "\n", _stage1SourceFiles ) + "\n").getBytes(),
227+
StandardOpenOption.APPEND
228+
);
229+
}
230+
200231
final long stage1LastModified = compile(
201232
stage0LastModified, stage1Classpath, stage1Target, earlyDeps, stage1SourceFiles
202233
);

stage1/ContextImplementation.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ class ContextImplementation(
1616
override val cbtRootHome: File,
1717
override val compatibilityTarget: File,
1818
override val parentBuildOrNull: BuildInterface,
19-
override val triggerLoopFilesArray: Array[File]
19+
override val loop: Boolean
2020
) extends Context{
2121
@deprecated("this method is replaced by workingDirectory","")
2222
def projectDirectory = workingDirectory

stage1/Stage1.scala

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ class Stage2Args(
4242
val cache: File,
4343
val cbtHome: File,
4444
val compatibilityTarget: File,
45-
val stage2sourceFiles: Seq[File]
45+
val stage2sourceFiles: Seq[File],
46+
val loop: Boolean
4647
)(
4748
implicit val transientCache: java.util.Map[AnyRef,AnyRef], val classLoaderCache: ClassLoaderCache, val logger: Logger
4849
){
@@ -60,7 +61,9 @@ object Stage1{
6061
val (_, cbtLastModified, classLoader) = buildStage2(
6162
buildStage1,
6263
context.cbtHome,
63-
context.cache
64+
context.cache,
65+
context.cwd,
66+
context.loop
6467
)(context.transientCache, new ClassLoaderCache( context.persistentCache ), logger)
6568

6669
classLoader
@@ -75,7 +78,7 @@ object Stage1{
7578
}
7679

7780
def buildStage2(
78-
buildStage1: BuildStage1Result, cbtHome: File, cache: File
81+
buildStage1: BuildStage1Result, cbtHome: File, cache: File, cwd: File, loop: Boolean
7982
)(
8083
implicit transientCache: java.util.Map[AnyRef,AnyRef], classLoaderCache: ClassLoaderCache, logger: Logger
8184
): (Seq[File], Long, ClassLoader) = {
@@ -99,6 +102,8 @@ object Stage1{
99102
)
100103

101104
logger.stage1("Compiling stage2 if necessary")
105+
if(loop)
106+
lib.addLoopFiles( cwd, stage2sourceFiles.toSet )
102107
val Some( stage2LastModified ) = compile(
103108
buildStage1.stage1LastModified,
104109
stage2sourceFiles, stage2Target, stage2StatusFile,
@@ -158,6 +163,7 @@ object Stage1{
158163
_args: Array[String],
159164
cache: File,
160165
cbtHome: File,
166+
loop: Boolean,
161167
buildStage1: BuildStage1Result,
162168
persistentCache: java.util.Map[AnyRef,AnyRef]
163169
): Int = {
@@ -168,17 +174,19 @@ object Stage1{
168174
implicit val transientCache: java.util.Map[AnyRef,AnyRef] = new java.util.HashMap
169175
implicit val classLoaderCache = new ClassLoaderCache( persistentCache )
170176

171-
val (stage2sourceFiles, stage2LastModified, classLoader) = buildStage2( buildStage1, cbtHome, cache )
177+
val cwd = new File( args.args(0) )
178+
val (stage2sourceFiles, stage2LastModified, classLoader) = buildStage2( buildStage1, cbtHome, cache, cwd, loop )
172179

173180
val stage2Args = new Stage2Args(
174-
new File( args.args(0) ),
175-
args.args.drop(1).toVector,
181+
cwd,
182+
args.args.drop(2).toVector,
176183
// launcher changes cause entire nailgun restart, so no need for them here
177184
stage2LastModified = stage2LastModified,
178185
cache,
179186
cbtHome,
180187
new File(buildStage1.compatibilityClasspath),
181-
stage2sourceFiles
188+
stage2sourceFiles,
189+
loop
182190
)
183191

184192
logger.stage1(s"Run Stage2")

0 commit comments

Comments
 (0)