-
Notifications
You must be signed in to change notification settings - Fork 77
Use the MacOS directory watcher on Windows too. #2272
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Package publishing
Documentation at https://github.com/dart-lang/ecosystem/wiki/Publishing-automation. |
6d7cbf4 to
2f66c21
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
This is an excellent pull request that unifies the directory watcher implementations for macOS and Windows. By removing the old Windows-specific code and adapting the macOS watcher to be cross-platform, you've significantly improved the maintainability of the codebase. The changes thoughtfully address Windows-specific filesystem quirks, and the refactoring of utility files into event_batching.dart, paths.dart, and testing.dart greatly improves code organization. The test suite has also been enhanced with better utilities and new tests for edge cases like relative paths and case-insensitive filesystems. I have a few minor suggestions to further polish this work, mainly around documentation and test cleanup. Overall, this is a high-quality and impactful change.
PR HealthLicense Headers ✔️
All source files should start with a license header. Unrelated files missing license headers
This check can be disabled by tagging the PR with API leaks ✔️The following packages contain symbols visible in the public API, but not exported by the library. Export these symbols or remove them from your publicly visible API.
This check can be disabled by tagging the PR with
Breaking changes
|
| Package | Change | Current Version | New Version | Needed Version | Looking good? |
|---|---|---|---|---|---|
| watcher | Non-Breaking | 1.1.4 | 1.1.5-wip | 1.2.0 Got "1.1.5-wip" expected >= "1.2.0" (non-breaking changes) |
This check can be disabled by tagging the PR with skip-breaking-check.
Coverage ⚠️
| File | Coverage |
|---|---|
| pkgs/watcher/lib/src/directory_watcher.dart | 💚 50 % |
| pkgs/watcher/lib/src/directory_watcher/directory_list.dart | 💚 68 % ⬆️ 1 % |
| pkgs/watcher/lib/src/directory_watcher/event_tree.dart | 💚 69 % |
| pkgs/watcher/lib/src/directory_watcher/linux/native_watch.dart | 💚 100 % |
| pkgs/watcher/lib/src/directory_watcher/linux/watch_tree.dart | 💚 96 % |
| pkgs/watcher/lib/src/directory_watcher/linux/watch_tree_root.dart | 💚 94 % |
| pkgs/watcher/lib/src/directory_watcher/macos/directory_tree.dart | 💔 0 % ⬇️ NaN % |
| pkgs/watcher/lib/src/directory_watcher/macos/native_watch.dart | 💔 0 % ⬇️ NaN % |
| pkgs/watcher/lib/src/directory_watcher/macos/watched_directory_tree.dart | 💔 0 % ⬇️ NaN % |
| pkgs/watcher/lib/src/directory_watcher/polling.dart | 💔 78 % ⬇️ 6 % |
| pkgs/watcher/lib/src/directory_watcher/windows.dart | 💔 0 % ⬇️ NaN % |
| pkgs/watcher/lib/src/directory_watcher/windows_isolate_directory_watcher.dart | 💔 0 % ⬇️ NaN % |
| pkgs/watcher/lib/src/event.dart | 💚 73 % ⬆️ 8 % |
| pkgs/watcher/lib/src/event_batching.dart | 💚 100 % |
| pkgs/watcher/lib/src/file_watcher/native.dart | 💚 81 % |
| pkgs/watcher/lib/src/paths.dart | 💚 71 % |
| pkgs/watcher/lib/src/testing.dart | 💚 25 % |
This check for test coverage is informational (issues shown here will not fail the PR).
This check can be disabled by tagging the PR with skip-coverage-check.
Changelog Entry ✔️
| Package | Changed Files |
|---|
Changes to files need to be accounted for in their respective changelogs.
This check can be disabled by tagging the PR with skip-changelog-check.
2f66c21 to
173dc87
Compare
df16638 to
6d951da
Compare
6d951da to
8f5dae4
Compare
| (event) { | ||
| // Only look at events for [watchedDirectory]. | ||
| final eventPath = AbsolutePath(event.path); | ||
| if (AbsolutePath(event.path).basename != watchedDirectory.basename) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| if (AbsolutePath(event.path).basename != watchedDirectory.basename) { | |
| if (eventPath.basename != watchedDirectory.basename) { |
| // Wait to work around https://github.com/dart-lang/sdk/issues/61378. | ||
| // Give the VM time to reset state after the error. See the issue for | ||
| // more discussion of the workaround. | ||
| await _subscription?.cancel(); | ||
| await Future<void>.delayed(const Duration(milliseconds: 1)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we have an issue or a TODO filed to remove this? Once we are OK bumping the min SDK in this package can we remove this because the linked issue is resolved?
| _ready(); | ||
| nativeWatch.close(); | ||
| _eventsController.close(); | ||
| if (!_eventsController.isClosed) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was this something that could have been fixed before this change, or is it specifically happening because of the two code paths getting merged?
| } | ||
|
|
||
| /// As [checkAndConvert] but also splits up move events. | ||
| static List<Event> checkAndConvertAndSplitMoves(FileSystemEvent event) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nit] IMO this name is less readable than if the operations were split up. Consider using an extension method if you prefer it to read as checkAndConvert(event).splitMoves().
| final result = tryRelativeTo(root); | ||
| if (result == null) { | ||
| throw ArgumentError('$this relativeTo $root'); | ||
| } | ||
| if (_string == root._string) return RelativePath(''); | ||
| return RelativePath(_string.substring(root._string.length + 1)); | ||
| return result; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[optional]
| final result = tryRelativeTo(root); | |
| if (result == null) { | |
| throw ArgumentError('$this relativeTo $root'); | |
| } | |
| if (_string == root._string) return RelativePath(''); | |
| return RelativePath(_string.substring(root._string.length + 1)); | |
| return result; | |
| return tryRelativeTo(root) ?? | |
| (throw ArgumentError('$this relativeTo $root')); |
| final List<Event> _events = []; | ||
| DateTime _lastUpdated; | ||
|
|
||
| _BufferedEvents() : _lastUpdated = overridableDateTimeNow(); | ||
|
|
||
| void add(Event event) { | ||
| _events.add(event); | ||
| _lastUpdated = overridableDateTimeNow(); | ||
| } | ||
|
|
||
| DateTime get lastUpdated => _lastUpdated; | ||
|
|
||
| List<Event> get events => _events; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nit]
| final List<Event> _events = []; | |
| DateTime _lastUpdated; | |
| _BufferedEvents() : _lastUpdated = overridableDateTimeNow(); | |
| void add(Event event) { | |
| _events.add(event); | |
| _lastUpdated = overridableDateTimeNow(); | |
| } | |
| DateTime get lastUpdated => _lastUpdated; | |
| List<Event> get events => _events; | |
| final List<Event> events = []; | |
| DateTime _lastUpdated; | |
| _BufferedEvents() : _lastUpdated = overridableDateTimeNow(); | |
| void add(Event event) { | |
| _events.add(event); | |
| _lastUpdated = overridableDateTimeNow(); | |
| } | |
| DateTime get lastUpdated => _lastUpdated; |
| // Use the current directory name as a check to ensure both don't run at | ||
| // the same time. All other tests must use absolute paths. | ||
| final testDirectory = Directory(p.join(d.sandbox, 'for_relative_test')); | ||
| testDirectory.createSync(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nit] Prefer to use the test_descriptor APIs to make changes under the sandbox directory.
| // Testing with relative paths is hard! There's only one current directory | ||
| // across the whole VM, tests can run in different isolates, and this test | ||
| // runs twice: once with the native watcher and once with the polling one. | ||
| // Use the current directory name as a check to ensure both don't run at |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This still has a race condition - I imagine that's at least part of the reason for the long 1 second delay before retries?
Should we instead be running with -j 1? Or should we use a file lock to remove the race condition?
| }); | ||
|
|
||
| group('on case-insensitive filesystem', () { | ||
| /// Whether the test filesystem is case sensitive. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nit] Don't use 3 slashes for local method definitions since they don't get read by dart doc.
| /// Whether the test filesystem is case sensitive. | |
| // Whether the test filesystem is case sensitive. |
Or better yet remove - I think it's redundant with the name.
| } | ||
|
|
||
| test('events with case-only changes', () async { | ||
| if (filesystemIsCaseSensitive()) return; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about checking the behavior of the file system before tests start, and use skip arguments?
(I guess
dart-ecosystem-teamwas added to the review due to the min SDK version bump and corresponding test workflow change, hi!)The MacOS directory watcher is fundamentally the same as the Windows one: both watch the root recursively and need to poll the filesystem to learn more about events. It turns out that it can work for both :) with some Windows-specific fixes patched in.
Remove the Windows watcher code, use the Mac one, adapt it as needed.
I didn't rename the Mac watcher files as that would make the diffs hard to read, will do that in a follow-up.
utils.dart, remove a few things to the one place they are used and event batching code to its own file.event_batching.dartand add a test. Add buffering by path version that's equivalent to what the Windows watcher was doing, except that it checks all buffered events via a single timer instead of maintaining+resetting one timer per path.unix_pathssupport Windows path separators too, rename topaths. Add test coverage around relative paths / filesystem roots to guard against this breaking something Windows-specific.fake_asyncpackage to test event batching with buffering by path, this introduces a min SDK version of 3.4 so increase the min SDK version.PathSetis no longer used, replaced by the nestedDirectoryTreeinstances in the combined Mac+Windows watcher, remove it + test.