From 9329b82deea8beeadb90a233bf5a74ad8db76e7c Mon Sep 17 00:00:00 2001 From: Rob Hogan Date: Wed, 28 Sep 2022 07:53:04 -0700 Subject: [PATCH] Support `fsevents` watcher on Apple silicon (#875) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: If Watchman is unavailable or disabled, we try to fall back to `FSEventsWatcher` and then to `NodeWatcher`. `FSEventsWatcher` is only available on `darwin`, so we `require` it in a try/catch and swallow any error. This was hiding the fact that `fsevents` was also failing to load on Apple silicon: ``` dlopen(/Users/robhogan/gh/metro/node_modules/fsevents/fsevents.node, 0x0001): tried: '/Users/robhogan/gh/metro/node_modules/fsevents/fsevents.node' (mach-o file, but is an incompatible architecture (have (x86_64), need (arm64e))) ``` `fsevents@2.3` introduces support for Apple silicon. This updates to that version, and also adds Flow types based on the TypeScript types bundled with `fsevents` so that Flow check passes on `darwin` (the Flow error was `untyped-import` so the existing `cannot-resolve-module` suppression doesn't work). Pull Request resolved: https://github.com/facebook/metro/pull/875 Test Plan: ### After: ``` $ node packages/metro/src/cli serve -c ../dummyproj/metro.config.js ▒▒▓▓▓▓▒▒ ▒▓▓▓▒▒░░▒▒▓▓▓▒ ▒▓▓▓▓░░░▒▒▒▒░░░▓▓▓▓▒ ▓▓▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒▓▓ ▓▓░░░░░▒▓▓▓▓▓▓▒░░░░░▓▓ ▓▓░░▓▓▒░░░▒▒░░░▒▓▒░░▓▓ ▓▓░░▓▓▓▓▓▒▒▒▒▓▓▓▓▒░░▓▓ ▓▓░░▓▓▓▓▓▓▓▓▓▓▓▓▓▒░░▓▓ ▓▓▒░░▒▒▓▓▓▓▓▓▓▓▒░░░▒▓▓ ▒▓▓▓▒░░░▒▓▓▒░░░▒▓▓▓▒ ▒▓▓▓▒░░░░▒▓▓▓▒ ▒▒▓▓▓▓▒▒ Welcome to Metro v0.72.3 Fast - Scalable - Integrated Metro:FSEventsWatcher Watching /Users/robhogan/gh/dummyproj +0ms ``` Reviewed By: huntie Differential Revision: D39883504 Pulled By: huntie fbshipit-source-id: 223a390715f13f32cdc1788185c75a96433581de --- flow-typed/fsevents.js | 73 +++++++++++++++++++ packages/metro-file-map/package.json | 2 +- .../src/watchers/FSEventsWatcher.js | 17 +++-- 3 files changed, 85 insertions(+), 7 deletions(-) create mode 100644 flow-typed/fsevents.js diff --git a/flow-typed/fsevents.js b/flow-typed/fsevents.js new file mode 100644 index 0000000000..6f3b0e2a75 --- /dev/null +++ b/flow-typed/fsevents.js @@ -0,0 +1,73 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict + * @format + * @oncall react_native + */ + +declare module 'fsevents' { + declare type Event = + | 'created' + | 'cloned' + | 'modified' + | 'deleted' + | 'moved' + | 'root-changed' + | 'unknown'; + + declare type Type = 'file' | 'directory' | 'symlink'; + + declare type FileChanges = { + inode: boolean, + finder: boolean, + access: boolean, + xattrs: boolean, + }; + + declare type Info = { + event: Event, + path: string, + type: Type, + changes: FileChanges, + flags: number, + }; + + declare type WatchHandler = (path: string, flags: number, id: string) => void; + + declare type FSEvents = { + watch(path: string, handler: WatchHandler): () => Promise, + getInfo(path: string, flags: number): Info, + constants: { + None: 0x00000000, + MustScanSubDirs: 0x00000001, + UserDropped: 0x00000002, + KernelDropped: 0x00000004, + EventIdsWrapped: 0x00000008, + HistoryDone: 0x00000010, + RootChanged: 0x00000020, + Mount: 0x00000040, + Unmount: 0x00000080, + ItemCreated: 0x00000100, + ItemRemoved: 0x00000200, + ItemInodeMetaMod: 0x00000400, + ItemRenamed: 0x00000800, + ItemModified: 0x00001000, + ItemFinderInfoMod: 0x00002000, + ItemChangeOwner: 0x00004000, + ItemXattrMod: 0x00008000, + ItemIsFile: 0x00010000, + ItemIsDir: 0x00020000, + ItemIsSymlink: 0x00040000, + ItemIsHardlink: 0x00100000, + ItemIsLastHardlink: 0x00200000, + OwnEvent: 0x00080000, + ItemCloned: 0x00400000, + }, + }; + + declare module.exports: FSEvents; +} diff --git a/packages/metro-file-map/package.json b/packages/metro-file-map/package.json index bdb709751c..9df1d82393 100644 --- a/packages/metro-file-map/package.json +++ b/packages/metro-file-map/package.json @@ -30,6 +30,6 @@ "slash": "^3.0.0" }, "optionalDependencies": { - "fsevents": "^2.1.2" + "fsevents": "^2.3.2" } } diff --git a/packages/metro-file-map/src/watchers/FSEventsWatcher.js b/packages/metro-file-map/src/watchers/FSEventsWatcher.js index a02e15ed54..6dbd77af96 100644 --- a/packages/metro-file-map/src/watchers/FSEventsWatcher.js +++ b/packages/metro-file-map/src/watchers/FSEventsWatcher.js @@ -8,6 +8,9 @@ * @flow strict-local */ +// $FlowFixMe[cannot-resolve-module] - Optional, Darwin only +import type {FSEvents} from 'fsevents'; + // $FlowFixMe[untyped-import] - anymatch import anymatch from 'anymatch'; import EventEmitter from 'events'; @@ -19,10 +22,11 @@ import walker from 'walker'; // $FlowFixMe[untyped-import] - micromatch const micromatch = require('micromatch'); +const debug = require('debug')('Metro:FSEventsWatcher'); + type Matcher = typeof anymatch.Matcher; -// $FlowFixMe[unclear-type] - fsevents -let fsevents: any = null; +let fsevents: ?FSEvents = null; try { // $FlowFixMe[cannot-resolve-module] - Optional, Darwin only fsevents = require('fsevents'); @@ -116,12 +120,13 @@ export default class FSEventsWatcher extends EventEmitter { this.doIgnore = opts.ignored ? anymatch(opts.ignored) : () => false; this.root = path.resolve(dir); - this.fsEventsWatchStopper = fsevents.watch( - this.root, - // $FlowFixMe[method-unbinding] - Refactor - this._handleEvent.bind(this), + + this.fsEventsWatchStopper = fsevents.watch(this.root, path => + this._handleEvent(path), ); + debug(`Watching ${this.root}`); + this._tracked = new Set(); FSEventsWatcher._recReaddir( this.root,