@@ -22,9 +22,13 @@ object Watching {
22
22
def apply (enterKeyPressed : Boolean , previousState : Option [T ]): Result [T ]
23
23
}
24
24
25
+ /**
26
+ * @param useNotify whether to use filesystem based watcher. If it is false uses polling.
27
+ */
25
28
case class WatchArgs (
26
29
setIdle : Boolean => Unit ,
27
- colors : Colors
30
+ colors : Colors ,
31
+ useNotify : Boolean
28
32
)
29
33
30
34
def watchLoop [T ](
@@ -71,8 +75,14 @@ object Watching {
71
75
if (alreadyStale) {
72
76
enterKeyPressed = false
73
77
} else {
74
- enterKeyPressed =
75
- watchAndWait(streams, watchArgs.setIdle, streams.in, watchables, watchArgs.colors)
78
+ enterKeyPressed = watchAndWait(
79
+ streams,
80
+ watchArgs.setIdle,
81
+ streams.in,
82
+ watchables,
83
+ watchArgs.colors,
84
+ useNotify = watchArgs.useNotify
85
+ )
76
86
}
77
87
}
78
88
throw new IllegalStateException (" unreachable" )
@@ -84,7 +94,8 @@ object Watching {
84
94
setIdle : Boolean => Unit ,
85
95
stdin : InputStream ,
86
96
watched : Seq [Watchable ],
87
- colors : Colors
97
+ colors : Colors ,
98
+ useNotify : Boolean
88
99
): Boolean = {
89
100
setIdle(true )
90
101
val (watchedPollables, watchedPathsSeq) = watched.partitionMap {
@@ -97,75 +108,86 @@ object Watching {
97
108
val watchedValueStr =
98
109
if (watchedValueCount == 0 ) " " else s " and $watchedValueCount other values "
99
110
100
- streams.err.println(
111
+ streams.err.println {
112
+ val viaFsNotify = if (useNotify) " (via fsnotify)" else " "
101
113
colors.info(
102
- s " Watching for changes to ${watchedPathsSeq.size} paths $watchedValueStr... (Enter to re-run, Ctrl-C to exit) "
114
+ s " Watching for changes to ${watchedPathsSeq.size} paths $viaFsNotify$ watchedValueStr... (Enter to re-run, Ctrl-C to exit) "
103
115
).toString
104
- )
105
-
106
- @ volatile var pathChangesDetected = false
107
-
108
- // oslib watch only works with folders, so we have to watch the parent folders instead
116
+ }
109
117
110
- if (enableDebugLog) DebugLog .println(
111
- colors.info(
112
- s " [watch:watched-paths:unfiltered] ${watchedPathsSet.toSeq.sorted.mkString(" \n " )}"
113
- ).toString
114
- )
115
-
116
- /** A hardcoded list of folders to ignore that we know have no impact on the build. */
117
- val ignoredFolders = Seq (
118
- mill.api.WorkspaceRoot .workspaceRoot / " out" ,
119
- mill.api.WorkspaceRoot .workspaceRoot / " .bloop" ,
120
- mill.api.WorkspaceRoot .workspaceRoot / " .metals" ,
121
- mill.api.WorkspaceRoot .workspaceRoot / " .idea" ,
122
- mill.api.WorkspaceRoot .workspaceRoot / " .git" ,
123
- mill.api.WorkspaceRoot .workspaceRoot / " .bsp"
124
- )
125
- if (enableDebugLog) DebugLog .println(
126
- colors.info(s " [watch:ignored-paths] ${ignoredFolders.toSeq.sorted.mkString(" \n " )}" ).toString
127
- )
128
-
129
- val osLibWatchPaths = watchedPathsSet.iterator.map(p => p / " .." ).toSet
130
- if (enableDebugLog) DebugLog .println(
131
- colors.info(s " [watch:watched-paths] ${osLibWatchPaths.toSeq.sorted.mkString(" \n " )}" ).toString
132
- )
133
-
134
- Using .resource(os.watch.watch(
135
- osLibWatchPaths.toSeq,
136
- filter = path => {
137
- val shouldBeIgnored = ignoredFolders.exists(ignored => path.startsWith(ignored))
138
- if (enableDebugLog) {
139
- val ignoredFoldersStr = ignoredFolders.mkString(" [\n " , " \n " , " \n ]" )
140
- DebugLog .println(
141
- colors.info(s " [watch:filter] $path (ignored= $shouldBeIgnored), ignoredFolders= $ignoredFoldersStr" ).toString
142
- )
143
- }
144
- ! shouldBeIgnored
145
- },
146
- onEvent = changedPaths => {
147
- // Make sure that the changed paths are actually the ones in our watch list and not some adjacent files in the
148
- // same folder
149
- val hasWatchedPath =
150
- changedPaths.exists(p => watchedPathsSet.exists(watchedPath => p.startsWith(watchedPath)))
151
- if (enableDebugLog) DebugLog .println(colors.info(
152
- s " [watch:changed-paths] (hasWatchedPath= $hasWatchedPath) ${changedPaths.mkString(" \n " )}"
153
- ).toString)
154
- if (hasWatchedPath) {
155
- pathChangesDetected = true
156
- }
157
- },
158
- logger =
159
- if (enableDebugLog) (eventType, data) => {
160
- DebugLog .println(colors.info(s " [watch] $eventType: ${pprint.apply(data)}" ).toString)
161
- }
162
- else (_, _) => {}
163
- )) { _ =>
164
- val enterKeyPressed =
165
- statWatchWait(watchedPollables, stdin, notifiablesChanged = () => pathChangesDetected)
118
+ def doWatch (notifiablesChanged : () => Boolean ) = {
119
+ val enterKeyPressed = statWatchWait(watchedPollables, stdin, notifiablesChanged)
166
120
setIdle(false )
167
121
enterKeyPressed
168
122
}
123
+
124
+ if (useNotify) {
125
+ @ volatile var pathChangesDetected = false
126
+
127
+ // oslib watch only works with folders, so we have to watch the parent folders instead
128
+
129
+ if (enableDebugLog) DebugLog .println(
130
+ colors.info(
131
+ s " [watch:watched-paths:unfiltered] ${watchedPathsSet.toSeq.sorted.mkString(" \n " )}"
132
+ ).toString
133
+ )
134
+
135
+ /** A hardcoded list of folders to ignore that we know have no impact on the build. */
136
+ val ignoredFolders = Seq (
137
+ mill.api.WorkspaceRoot .workspaceRoot / " out" ,
138
+ mill.api.WorkspaceRoot .workspaceRoot / " .bloop" ,
139
+ mill.api.WorkspaceRoot .workspaceRoot / " .metals" ,
140
+ mill.api.WorkspaceRoot .workspaceRoot / " .idea" ,
141
+ mill.api.WorkspaceRoot .workspaceRoot / " .git" ,
142
+ mill.api.WorkspaceRoot .workspaceRoot / " .bsp"
143
+ )
144
+ if (enableDebugLog) DebugLog .println(
145
+ colors.info(s " [watch:ignored-paths] ${ignoredFolders.toSeq.sorted.mkString(" \n " )}" ).toString
146
+ )
147
+
148
+ val osLibWatchPaths = watchedPathsSet.iterator.map(p => p / " .." ).toSet
149
+ if (enableDebugLog) DebugLog .println(
150
+ colors.info(s " [watch:watched-paths] ${osLibWatchPaths.toSeq.sorted.mkString(" \n " )}" ).toString
151
+ )
152
+
153
+ Using .resource(os.watch.watch(
154
+ osLibWatchPaths.toSeq,
155
+ filter = path => {
156
+ val shouldBeIgnored = ignoredFolders.exists(ignored => path.startsWith(ignored))
157
+ if (enableDebugLog) {
158
+ val ignoredFoldersStr = ignoredFolders.mkString(" [\n " , " \n " , " \n ]" )
159
+ DebugLog .println(
160
+ colors.info(
161
+ s " [watch:filter] $path (ignored= $shouldBeIgnored), ignoredFolders= $ignoredFoldersStr"
162
+ ).toString
163
+ )
164
+ }
165
+ ! shouldBeIgnored
166
+ },
167
+ onEvent = changedPaths => {
168
+ // Make sure that the changed paths are actually the ones in our watch list and not some adjacent files in the
169
+ // same folder
170
+ val hasWatchedPath =
171
+ changedPaths.exists(p => watchedPathsSet.exists(watchedPath => p.startsWith(watchedPath)))
172
+ if (enableDebugLog) DebugLog .println(colors.info(
173
+ s " [watch:changed-paths] (hasWatchedPath= $hasWatchedPath) ${changedPaths.mkString(" \n " )}"
174
+ ).toString)
175
+ if (hasWatchedPath) {
176
+ pathChangesDetected = true
177
+ }
178
+ },
179
+ logger =
180
+ if (enableDebugLog) (eventType, data) => {
181
+ DebugLog .println(colors.info(s " [watch] $eventType: ${pprint.apply(data)}" ).toString)
182
+ }
183
+ else (_, _) => {}
184
+ )) { _ =>
185
+ doWatch(notifiablesChanged = () => pathChangesDetected)
186
+ }
187
+ }
188
+ else {
189
+ doWatch(notifiablesChanged = () => watchedPathsSeq.exists(p => ! p.validate()))
190
+ }
169
191
}
170
192
171
193
/**
0 commit comments