github.com/tilt-dev/tilt@v0.36.0/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  				isDir := e.Flags&fsevents.ItemIsDir == fsevents.ItemIsDir
    42  				if isDir && isPathWereWatching {
    43  					// For consistency with Linux and Windows, don't fire any events
    44  					// for directories that we're watching -- only their contents.
    45  					continue
    46  				}
    47  
    48  				// On MacOS, modifying a directory entry fires Created | InodeMetaMod
    49  				// Ignore these events, mod time modifications shouldnt trigger copies.
    50  				if isDir && (e.Flags&fsevents.ItemInodeMetaMod) == fsevents.ItemInodeMetaMod {
    51  					continue
    52  				}
    53  
    54  				ignore, err := d.ignore.Matches(e.Path)
    55  				if err != nil {
    56  					d.logger.Infof("Error matching path %q: %v", e.Path, err)
    57  				} else if ignore {
    58  					continue
    59  				}
    60  
    61  				d.events <- NewFileEvent(e.Path)
    62  			}
    63  		}
    64  	}
    65  }
    66  
    67  // Add a path to be watched. Should only be called during initialization.
    68  func (d *darwinNotify) initAdd(name string) {
    69  	d.stream.Paths = append(d.stream.Paths, name)
    70  
    71  	if d.pathsWereWatching == nil {
    72  		d.pathsWereWatching = make(map[string]interface{})
    73  	}
    74  	d.pathsWereWatching[name] = struct{}{}
    75  }
    76  
    77  func (d *darwinNotify) Start() error {
    78  	if len(d.stream.Paths) == 0 {
    79  		return nil
    80  	}
    81  
    82  	numberOfWatches.Add(int64(len(d.stream.Paths)))
    83  
    84  	if err := d.stream.Start(); err != nil {
    85  		return err
    86  	}
    87  
    88  	go d.loop()
    89  
    90  	return nil
    91  }
    92  
    93  func (d *darwinNotify) Close() error {
    94  	numberOfWatches.Add(int64(-len(d.stream.Paths)))
    95  
    96  	d.stream.Stop()
    97  	close(d.errors)
    98  	close(d.stop)
    99  
   100  	return nil
   101  }
   102  
   103  func (d *darwinNotify) Events() chan FileEvent {
   104  	return d.events
   105  }
   106  
   107  func (d *darwinNotify) Errors() chan error {
   108  	return d.errors
   109  }
   110  
   111  func newWatcher(paths []string, ignore PathMatcher, l logger.Logger) (*darwinNotify, error) {
   112  	dw := &darwinNotify{
   113  		ignore: ignore,
   114  		logger: l,
   115  		stream: &fsevents.EventStream{
   116  			Latency: 1 * time.Millisecond,
   117  			Flags:   fsevents.FileEvents,
   118  			// NOTE(dmiller): this corresponds to the `sinceWhen` parameter in FSEventStreamCreate
   119  			// https://developer.apple.com/documentation/coreservices/1443980-fseventstreamcreate
   120  			EventID: fsevents.LatestEventID(),
   121  		},
   122  		events: make(chan FileEvent),
   123  		errors: make(chan error),
   124  		stop:   make(chan struct{}),
   125  	}
   126  
   127  	paths = dedupePathsForRecursiveWatcher(paths)
   128  	for _, path := range paths {
   129  		path, err := filepath.Abs(path)
   130  		if err != nil {
   131  			return nil, errors.Wrap(err, "newWatcher")
   132  		}
   133  		dw.initAdd(path)
   134  	}
   135  
   136  	return dw, nil
   137  }
   138  
   139  var _ Notify = &darwinNotify{}