diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 00000000..6b704bbb --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,80 @@ +version: 2.1 +workflows: + main: + jobs: ['linux-arm64', 'ios'] + +jobs: + # linux/arm64 + linux-arm64: + machine: + image: ubuntu-2204:2022.07.1 + resource_class: arm.medium + working_directory: ~/repo + steps: + - checkout + + - run: + name: install-go + command: | + sudo apt -y install golang + + - run: + name: test + command: | + uname -a + go version + go test -race ./... + + # iOS + ios: + macos: + xcode: 12.5.1 + working_directory: ~/repo + steps: + - checkout + + - run: + name: install-go + command: | + export HOMEBREW_NO_AUTO_UPDATE=1 + brew install go + + - run: + name: test + environment: + SCAN_DEVICE: iPhone 6 + SCAN_SCHEME: WebTests + command: | + export PATH=$PATH:/usr/local/Cellar/go/*/bin + uname -a + go version + go test -race ./... + + # This is just Linux x86_64; also need to get a Go with GOOS=android, but + # there aren't any pre-built versions of that on the Go site. Idk, disable for + # now; number of people using Go on Android is probably very tiny, and the + # number of people using Go with this lib smaller still. + # android: + # machine: + # image: android:2022.01.1 + # working_directory: ~/repo + # steps: + # - checkout + + # - run: + # name: install-go + # command: | + # v=1.19.2 + # curl --silent --show-error --location --fail --retry 3 --output /tmp/go${v}.tgz \ + # "https://go.dev/dl/go$v.linux-arm64.tar.gz" + # sudo tar -C /usr/local -xzf /tmp/go${v}.tgz + # rm /tmp/go${v}.tgz + + # - run: + # name: test + # command: | + # uname -a + # export PATH=/usr/local/go/bin:$PATH + # go version + # go test -race ./... + # diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4b20df61..e1caf851 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,7 +21,25 @@ jobs: - name: build run: | - set -x for a in $(go tool dist list); do - GOOS=${a%%/*} GOARCH=${a#*/} go build + export GOOS=${a%%/*} + export GOARCH=${a#*/} + + case "$GOOS" in + (android|ios) exit 0 ;; # Requires cgo to link. + (js) exit 0 ;; # No build tags in internal/ TODO: should maybe fix? + (openbsd) + case "$GOARCH" in + # Fails with: + # golang.org/x/sys/unix.syscall_syscall9: relocation target syscall.syscall10 not defined + # golang.org/x/sys/unix.kevent: relocation target syscall.syscall6 not defined + # golang.org/x/sys/unix.pipe2: relocation target syscall.rawSyscall not defined + # ... + # Works for me locally though, hmm... + (386) exit 0 ;; + esac + esac + + go test -c + go build ./cmd/fsnotify done diff --git a/.gitignore b/.gitignore index 1d89d85c..391cc076 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ # Output of go build ./cmd/fsnotify /fsnotify +/fsnotify.exe diff --git a/backend_other.go b/backend_other.go index e2b21427..bec67c52 100644 --- a/backend_other.go +++ b/backend_other.go @@ -8,8 +8,111 @@ import ( "runtime" ) -// Watcher watches a set of files, delivering events to a channel. -type Watcher struct{} +// Watcher watches a set of paths, delivering events on a channel. +// +// A watcher should not be copied (e.g. pass it by pointer, rather than by +// value). +// +// # Linux notes +// +// When a file is removed a Remove event won't be emitted until all file +// descriptors are closed, and deletes will always emit a Chmod. For example: +// +// fp := os.Open("file") +// os.Remove("file") // Triggers Chmod +// fp.Close() // Triggers Remove +// +// This is the event that inotify sends, so not much can be changed about this. +// +// The fs.inotify.max_user_watches sysctl variable specifies the upper limit +// for the number of watches per user, and fs.inotify.max_user_instances +// specifies the maximum number of inotify instances per user. Every Watcher you +// create is an "instance", and every path you add is a "watch". +// +// These are also exposed in /proc as /proc/sys/fs/inotify/max_user_watches and +// /proc/sys/fs/inotify/max_user_instances +// +// To increase them you can use sysctl or write the value to the /proc file: +// +// # Default values on Linux 5.18 +// sysctl fs.inotify.max_user_watches=124983 +// sysctl fs.inotify.max_user_instances=128 +// +// To make the changes persist on reboot edit /etc/sysctl.conf or +// /usr/lib/sysctl.d/50-default.conf (details differ per Linux distro; check +// your distro's documentation): +// +// fs.inotify.max_user_watches=124983 +// fs.inotify.max_user_instances=128 +// +// Reaching the limit will result in a "no space left on device" or "too many open +// files" error. +// +// # kqueue notes (macOS, BSD) +// +// kqueue requires opening a file descriptor for every file that's being watched; +// so if you're watching a directory with five files then that's six file +// descriptors. You will run in to your system's "max open files" limit faster on +// these platforms. +// +// The sysctl variables kern.maxfiles and kern.maxfilesperproc can be used to +// control the maximum number of open files, as well as /etc/login.conf on BSD +// systems. +// +// # macOS notes +// +// Spotlight indexing on macOS can result in multiple events (see [#15]). A +// temporary workaround is to add your folder(s) to the "Spotlight Privacy +// Settings" until we have a native FSEvents implementation (see [#11]). +// +// [#11]: https://github.com/fsnotify/fsnotify/issues/11 +// [#15]: https://github.com/fsnotify/fsnotify/issues/15 +type Watcher struct { + // Events sends the filesystem change events. + // + // fsnotify can send the following events; a "path" here can refer to a + // file, directory, symbolic link, or special file like a FIFO. + // + // fsnotify.Create A new path was created; this may be followed by one + // or more Write events if data also gets written to a + // file. + // + // fsnotify.Remove A path was removed. + // + // fsnotify.Rename A path was renamed. A rename is always sent with the + // old path as Event.Name, and a Create event will be + // sent with the new name. Renames are only sent for + // paths that are currently watched; e.g. moving an + // unmonitored file into a monitored directory will + // show up as just a Create. Similarly, renaming a file + // to outside a monitored directory will show up as + // only a Rename. + // + // fsnotify.Write A file or named pipe was written to. A Truncate will + // also trigger a Write. A single "write action" + // initiated by the user may show up as one or multiple + // writes, depending on when the system syncs things to + // disk. For example when compiling a large Go program + // you may get hundreds of Write events, so you + // probably want to wait until you've stopped receiving + // them (see the dedup example in cmd/fsnotify). + // + // fsnotify.Chmod Attributes were changed. On Linux this is also sent + // when a file is removed (or more accurately, when a + // link to an inode is removed). On kqueue it's sent + // and on kqueue when a file is truncated. On Windows + // it's never sent. + Events chan Event + + // Errors sends any errors. + // + // [ErrEventOverflow] is used to indicate ther are too many events: + // + // - inotify: there are too many queued events (fs.inotify.max_queued_events sysctl) + // - windows: The buffer size is too small. + // - kqueue, fen: not used. + Errors chan error +} // NewWatcher creates a new Watcher. func NewWatcher() (*Watcher, error) { diff --git a/internal/debug_openbsd.go b/internal/debug_openbsd.go index 996b4414..1dd455bc 100644 --- a/internal/debug_openbsd.go +++ b/internal/debug_openbsd.go @@ -7,7 +7,7 @@ var names = []struct { m uint32 }{ {"NOTE_ATTRIB", unix.NOTE_ATTRIB}, - {"NOTE_CHANGE", unix.NOTE_CHANGE}, + // {"NOTE_CHANGE", unix.NOTE_CHANGE}, // Not on 386? {"NOTE_CHILD", unix.NOTE_CHILD}, {"NOTE_DELETE", unix.NOTE_DELETE}, {"NOTE_EOF", unix.NOTE_EOF},