github.com/tilt-dev/tilt@v0.33.15-0.20240515162809-0a22ed45d8a0/internal/watch/watcher_darwin.go (about) 1 package watch 2 3 import ( 4 "path/filepath" 5 "time" 6 7 "github.com/pkg/errors" 8 9 "github.com/tilt-dev/tilt/pkg/logger" 10 11 "github.com/fsnotify/fsevents" 12 ) 13 14 // A file watcher optimized for Darwin. 15 // Uses FSEvents to avoid the terrible perf characteristics of kqueue. 16 type darwinNotify struct { 17 stream *fsevents.EventStream 18 events chan FileEvent 19 errors chan error 20 stop chan struct{} 21 22 pathsWereWatching map[string]interface{} 23 ignore PathMatcher 24 logger logger.Logger 25 } 26 27 func (d *darwinNotify) loop() { 28 for { 29 select { 30 case <-d.stop: 31 return 32 case events, ok := <-d.stream.Events: 33 if !ok { 34 return 35 } 36 37 for _, e := range events { 38 e.Path = filepath.Join("/", e.Path) 39 40 _, isPathWereWatching := d.pathsWereWatching[e.Path] 41 if e.Flags&fsevents.ItemIsDir == fsevents.ItemIsDir && isPathWereWatching { 42 // For consistency with Linux and Windows, don't fire any events 43 // for directories that we're watching -- only their contents. 44 continue 45 } 46 47 ignore, err := d.ignore.Matches(e.Path) 48 if err != nil { 49 d.logger.Infof("Error matching path %q: %v", e.Path, err) 50 } else if ignore { 51 continue 52 } 53 54 d.events <- NewFileEvent(e.Path) 55 } 56 } 57 } 58 } 59 60 // Add a path to be watched. Should only be called during initialization. 61 func (d *darwinNotify) initAdd(name string) { 62 d.stream.Paths = append(d.stream.Paths, name) 63 64 if d.pathsWereWatching == nil { 65 d.pathsWereWatching = make(map[string]interface{}) 66 } 67 d.pathsWereWatching[name] = struct{}{} 68 } 69 70 func (d *darwinNotify) Start() error { 71 if len(d.stream.Paths) == 0 { 72 return nil 73 } 74 75 numberOfWatches.Add(int64(len(d.stream.Paths))) 76 77 d.stream.Start() 78 79 go d.loop() 80 81 return nil 82 } 83 84 func (d *darwinNotify) Close() error { 85 numberOfWatches.Add(int64(-len(d.stream.Paths))) 86 87 d.stream.Stop() 88 close(d.errors) 89 close(d.stop) 90 91 return nil 92 } 93 94 func (d *darwinNotify) Events() chan FileEvent { 95 return d.events 96 } 97 98 func (d *darwinNotify) Errors() chan error { 99 return d.errors 100 } 101 102 func newWatcher(paths []string, ignore PathMatcher, l logger.Logger) (*darwinNotify, error) { 103 dw := &darwinNotify{ 104 ignore: ignore, 105 logger: l, 106 stream: &fsevents.EventStream{ 107 Latency: 1 * time.Millisecond, 108 Flags: fsevents.FileEvents, 109 // NOTE(dmiller): this corresponds to the `sinceWhen` parameter in FSEventStreamCreate 110 // https://developer.apple.com/documentation/coreservices/1443980-fseventstreamcreate 111 EventID: fsevents.LatestEventID(), 112 }, 113 events: make(chan FileEvent), 114 errors: make(chan error), 115 stop: make(chan struct{}), 116 } 117 118 paths = dedupePathsForRecursiveWatcher(paths) 119 for _, path := range paths { 120 path, err := filepath.Abs(path) 121 if err != nil { 122 return nil, errors.Wrap(err, "newWatcher") 123 } 124 dw.initAdd(path) 125 } 126 127 return dw, nil 128 } 129 130 var _ Notify = &darwinNotify{}