From e57b4240f54032f65e59a856741beac521ee30d7 Mon Sep 17 00:00:00 2001 From: Patrick Scott Date: Tue, 4 Feb 2025 09:46:36 -0500 Subject: [PATCH 01/27] Evaluate build tags as both true and false (#1938) Gazelle already iterates over all supported os/arch combos to include go files. This is similar but expanded to custom tags. When adding a new build tag, we create a copy with the tag set to true and append to the original. This means that build tag evaluation will grow exponentially. Fixes https://github.com/bazelbuild/bazel-gazelle/issues/1262 **What type of PR is this?** Feature **What package or component does this PR mostly affect?** language/go **What does this PR do? Why is it needed?** It changes the behavior of `build_tags` to defer evaluation to build time. Instead of assuming the tag is _always_ true, it treats the tag as both true and false when evaluating tags for a file. **Which issues(s) does this PR fix?** Fixes #1262 **Other notes for review** --- Design.rst | 4 ++-- README.rst | 10 ++++---- language/go/config.go | 45 +++++++++++++++++++----------------- language/go/config_test.go | 43 +++++++++++++++------------------- language/go/fileinfo.go | 14 +++++++---- language/go/fileinfo_test.go | 31 ++++++++++++------------- 6 files changed, 76 insertions(+), 71 deletions(-) diff --git a/Design.rst b/Design.rst index 5052fb169..4aa9ccd29 100644 --- a/Design.rst +++ b/Design.rst @@ -107,8 +107,8 @@ Here are a few examples. See the `full list of directives`_. * ``# gazelle:prefix`` - sets the Go import path prefix for the current directory. -* ``# gazelle:build_tags`` - sets the list of build tags which Gazelle considers - to be true on all platforms. +* ``# gazelle:build_tags`` - sets the list of build tags which Gazelle will + defer to Bazel for evaluation. There are a few directives which are not applied to the ``Config`` object but are interpreted directly in packages where they are relevant. diff --git a/README.rst b/README.rst index df00f07bf..f7dfe53d3 100644 --- a/README.rst +++ b/README.rst @@ -495,11 +495,12 @@ The following flags are accepted: +-------------------------------------------------------------------+----------------------------------------+ | :flag:`-build_tags tag1,tag2` | | +-------------------------------------------------------------------+----------------------------------------+ -| List of Go build tags Gazelle will consider to be true. Gazelle applies | +| List of Go build tags Gazelle will defer to Bazel for evaluation. Gazelle applies | | constraints when generating Go rules. It assumes certain tags are true on | | certain platforms (for example, ``amd64,linux``). It assumes all Go release | | tags are true (for example, ``go1.8``). It considers other tags to be false | -| (for example, ``ignore``). This flag overrides that behavior. | +| (for example, ``ignore``). This flag allows custom tags to be evaluated by | +| Bazel at build time. | | | | Bazel may still filter sources with these tags. Use | | ``bazel build --define gotags=foo,bar`` to set tags at build time. | @@ -761,11 +762,12 @@ The following directives are recognized: +---------------------------------------------------+----------------------------------------+ | :direc:`# gazelle:build_tags foo,bar` | none | +---------------------------------------------------+----------------------------------------+ -| List of Go build tags Gazelle will consider to be true. Gazelle applies | +| List of Go build tags Gazelle will defer to Bazel for evaluation. Gazelle applies | | constraints when generating Go rules. It assumes certain tags are true on | | certain platforms (for example, ``amd64,linux``). It assumes all Go release | | tags are true (for example, ``go1.8``). It considers other tags to be false | -| (for example, ``ignore``). This flag overrides that behavior. | +| (for example, ``ignore``). This flag allows custom tags to be evaluated by | +| Bazel at build time. | | | | Bazel may still filter sources with these tags. Use | | ``bazel build --define gotags=foo,bar`` to set tags at build time. | diff --git a/language/go/config.go b/language/go/config.go index d0a4007f6..89ac3f4d1 100644 --- a/language/go/config.go +++ b/language/go/config.go @@ -41,6 +41,16 @@ import ( var minimumRulesGoVersion = version.Version{0, 29, 0} +type tagSet map[string]struct{} + +func (ts tagSet) clone() tagSet { + c := make(tagSet, len(ts)) + for k, v := range ts { + c[k] = v + } + return c +} + // goConfig contains configuration values related to Go rules. type goConfig struct { // The name under which the rules_go repository can be referenced from the @@ -53,7 +63,7 @@ type goConfig struct { // genericTags is a set of tags that Gazelle considers to be true. Set with // -build_tags or # gazelle:build_tags. Some tags, like gc, are always on. - genericTags map[string]bool + genericTags []tagSet // prefix is a prefix of an import path, used to generate importpath // attributes. Set with -go_prefix or # gazelle:prefix. @@ -178,8 +188,10 @@ func newGoConfig() *goConfig { goProtoCompilers: defaultGoProtoCompilers, goGrpcCompilers: defaultGoGrpcCompilers, goGenerateProto: true, + genericTags: []tagSet{ + {"gc": struct{}{}}, + }, } - gc.preprocessTags() return gc } @@ -189,9 +201,9 @@ func getGoConfig(c *config.Config) *goConfig { func (gc *goConfig) clone() *goConfig { gcCopy := *gc - gcCopy.genericTags = make(map[string]bool) - for k, v := range gc.genericTags { - gcCopy.genericTags[k] = v + gcCopy.genericTags = make([]tagSet, 0, len(gc.genericTags)) + for _, ts := range gc.genericTags { + gcCopy.genericTags = append(gcCopy.genericTags, ts.clone()) } gcCopy.goProtoCompilers = gc.goProtoCompilers[:len(gc.goProtoCompilers):len(gc.goProtoCompilers)] gcCopy.goGrpcCompilers = gc.goGrpcCompilers[:len(gc.goGrpcCompilers):len(gc.goGrpcCompilers)] @@ -199,18 +211,8 @@ func (gc *goConfig) clone() *goConfig { return &gcCopy } -// preprocessTags adds some tags which are on by default before they are -// used to match files. -func (gc *goConfig) preprocessTags() { - if gc.genericTags == nil { - gc.genericTags = make(map[string]bool) - } - gc.genericTags["gc"] = true -} - // setBuildTags sets genericTags by parsing as a comma separated list. An // error will be returned for tags that wouldn't be recognized by "go build". -// preprocessTags should be called before this. func (gc *goConfig) setBuildTags(tags string) error { if tags == "" { return nil @@ -219,7 +221,13 @@ func (gc *goConfig) setBuildTags(tags string) error { if strings.HasPrefix(t, "!") { return fmt.Errorf("build tags can't be negated: %s", t) } - gc.genericTags[t] = true + var newSets []tagSet + for _, ts := range gc.genericTags { + c := ts.clone() + c[t] = struct{}{} + newSets = append(newSets, c) + } + gc.genericTags = append(gc.genericTags, newSets...) } return nil } @@ -584,11 +592,6 @@ Update io_bazel_rules_go to a newer version in your WORKSPACE file.` for _, d := range f.Directives { switch d.Key { case "build_tags": - if err := gc.setBuildTags(d.Value); err != nil { - log.Print(err) - continue - } - gc.preprocessTags() if err := gc.setBuildTags(d.Value); err != nil { log.Print(err) } diff --git a/language/go/config_test.go b/language/go/config_test.go index e00e81aad..3ac54d88e 100644 --- a/language/go/config_test.go +++ b/language/go/config_test.go @@ -59,6 +59,21 @@ func testConfig(t *testing.T, args ...string) (*config.Config, []language.Langua return c, langs, cexts } +func newTagSet(tags ...string) tagSet { + ts := make(tagSet) + for _, t := range tags { + ts[t] = struct{}{} + } + return ts +} + +var expectedBuildTags = []tagSet{ + newTagSet("gc"), + newTagSet("gc", "foo"), + newTagSet("gc", "bar"), + newTagSet("gc", "foo", "bar"), +} + func TestCommandLine(t *testing.T) { c, _, _ := testConfig( t, @@ -68,10 +83,8 @@ func TestCommandLine(t *testing.T) { "-external=vendored", "-repo_root=.") gc := getGoConfig(c) - for _, tag := range []string{"foo", "bar", "gc"} { - if !gc.genericTags[tag] { - t.Errorf("expected tag %q to be set", tag) - } + if diff := cmp.Diff(expectedBuildTags, gc.genericTags); diff != "" { + t.Errorf("(-want, +got): %s", diff) } if gc.prefix != "example.com/repo" { t.Errorf(`got prefix %q; want "example.com/repo"`, gc.prefix) @@ -101,10 +114,8 @@ func TestDirectives(t *testing.T) { cext.Configure(c, "test", f) } gc := getGoConfig(c) - for _, tag := range []string{"foo", "bar", "gc"} { - if !gc.genericTags[tag] { - t.Errorf("expected tag %q to be set", tag) - } + if diff := cmp.Diff(expectedBuildTags, gc.genericTags); diff != "" { + t.Errorf("(-want, +got): %s", diff) } if gc.prefix != "y" { t.Errorf(`got prefix %q; want "y"`, gc.prefix) @@ -268,22 +279,6 @@ load("@io_bazel_rules_go//proto:go_proto_library.bzl", "go_proto_library") } } -func TestPreprocessTags(t *testing.T) { - gc := newGoConfig() - expectedTags := []string{"gc"} - for _, tag := range expectedTags { - if !gc.genericTags[tag] { - t.Errorf("tag %q not set", tag) - } - } - unexpectedTags := []string{"x", "cgo", "go1.8", "go1.7"} - for _, tag := range unexpectedTags { - if gc.genericTags[tag] { - t.Errorf("tag %q unexpectedly set", tag) - } - } -} - func TestPrefixFallback(t *testing.T) { c, _, cexts := testConfig(t) for _, tc := range []struct { diff --git a/language/go/fileinfo.go b/language/go/fileinfo.go index 38a95665b..1d5d7c924 100644 --- a/language/go/fileinfo.go +++ b/language/go/fileinfo.go @@ -569,7 +569,7 @@ func checkConstraints(c *config.Config, os, arch, osSuffix, archSuffix string, t } goConf := getGoConfig(c) - checker := func(tag string) bool { + checker := func(tag string, ts tagSet) bool { if isIgnoredTag(tag) { return true } @@ -585,13 +585,19 @@ func checkConstraints(c *config.Config, os, arch, osSuffix, archSuffix string, t return false } return arch == tag - } - return goConf.genericTags[tag] + _, ok := ts[tag] + return ok } - return tags.eval(checker) && cgoTags.eval(checker) + for _, ts := range goConf.genericTags { + c := func(tag string) bool { return checker(tag, ts) } + if tags.eval(c) && cgoTags.eval(c) { + return true + } + } + return false } // rulesGoSupportsOS returns whether the os tag is recognized by the version of diff --git a/language/go/fileinfo_test.go b/language/go/fileinfo_test.go index b7c5c1d91..6c89ac650 100644 --- a/language/go/fileinfo_test.go +++ b/language/go/fileinfo_test.go @@ -386,7 +386,7 @@ func TestCheckConstraints(t *testing.T) { defer os.RemoveAll(dir) for _, tc := range []struct { desc string - genericTags map[string]bool + genericTags string os, arch, filename, content string want bool }{ @@ -466,12 +466,12 @@ func TestCheckConstraints(t *testing.T) { want: false, }, { desc: "tags all satisfied", - genericTags: map[string]bool{"a": true, "b": true}, + genericTags: "a,b", content: "// +build a,b\n\npackage foo", want: true, }, { desc: "tags some satisfied", - genericTags: map[string]bool{"a": true}, + genericTags: "a", content: "// +build a,b\n\npackage foo", want: false, }, { @@ -480,36 +480,36 @@ func TestCheckConstraints(t *testing.T) { want: true, }, { desc: "tag satisfied negated", - genericTags: map[string]bool{"a": true}, + genericTags: "a", content: "// +build !a\n\npackage foo", - want: false, + want: true, }, { desc: "tag double negative", content: "// +build !!a\n\npackage foo", want: false, }, { desc: "tag group and satisfied", - genericTags: map[string]bool{"foo": true, "bar": true}, + genericTags: "foo,bar", content: "// +build foo,bar\n\npackage foo", want: true, }, { desc: "tag group and unsatisfied", - genericTags: map[string]bool{"foo": true}, + genericTags: "foo", content: "// +build foo,bar\n\npackage foo", want: false, }, { desc: "tag line or satisfied", - genericTags: map[string]bool{"foo": true}, + genericTags: "foo", content: "// +build foo bar\n\npackage foo", want: true, }, { desc: "tag line or unsatisfied", - genericTags: map[string]bool{"foo": true}, + genericTags: "foo", content: "// +build !foo bar\n\npackage foo", - want: false, + want: true, }, { desc: "tag lines and satisfied", - genericTags: map[string]bool{"foo": true, "bar": true}, + genericTags: "foo,bar", content: ` // +build foo // +build bar @@ -518,7 +518,7 @@ package foo`, want: true, }, { desc: "tag lines and unsatisfied", - genericTags: map[string]bool{"foo": true}, + genericTags: "foo", content: ` // +build foo // +build bar @@ -528,7 +528,7 @@ package foo`, }, { desc: "cgo tags satisfied", os: "linux", - genericTags: map[string]bool{"foo": true}, + genericTags: "foo", content: ` // +build foo @@ -581,9 +581,8 @@ import "C" t.Run(tc.desc, func(t *testing.T) { c, _, _ := testConfig(t) gc := getGoConfig(c) - gc.genericTags = tc.genericTags - if gc.genericTags == nil { - gc.genericTags = map[string]bool{"gc": true} + if err := gc.setBuildTags(tc.genericTags); err != nil { + t.Errorf("error setting build tags %q", tc.genericTags) } filename := tc.filename if filename == "" { From ac5ae618cd24825b58be0ada28fc7b33767c5762 Mon Sep 17 00:00:00 2001 From: Jason Bedard Date: Wed, 5 Feb 2025 22:40:09 -0800 Subject: [PATCH 02/27] fix: correct updated filepath when updating expected output BUILD files (#2025) It seems 7f7af3b8d1942061c06850ff9a9115cab05b2665 broke `UPDATE_SNAPSHOTS` **What type of PR is this?** Bug fix **What package or component does this PR mostly affect?** all **What does this PR do? Why is it needed?** Fix `UPDATE_SNAPSHOTS` to write to the correct BUILD.out filepath. --- internal/generationtest/generation_test.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/internal/generationtest/generation_test.go b/internal/generationtest/generation_test.go index 6ef5d933f..57726e80d 100644 --- a/internal/generationtest/generation_test.go +++ b/internal/generationtest/generation_test.go @@ -65,7 +65,12 @@ func TestFullGeneration(t *testing.T) { if err != nil { t.Fatalf("Failed to find runfile %s. Error: %v", path, err) } + // absolutePathToTestDirectory is the absolute + // path to the test case directory. For example, /home//wksp/path/to/test_data/my_test_case absolutePathToTestDirectory := filepath.Dir(actualFilePath) + // relativePathToTestDirectory is the workspace relative path + // to this test case directory. For example, path/to/test_data/my_test_case + relativePathToTestDirectory := filepath.Dir(path[strings.IndexRune(path, '/')+1:]) // name is the name of the test directory. For example, my_test_case. // The name of the directory doubles as the name of the test. name := filepath.Base(absolutePathToTestDirectory) @@ -79,8 +84,7 @@ func TestFullGeneration(t *testing.T) { tests = append(tests, &testtools.TestGazelleGenerationArgs{ Name: name, TestDataPathAbsolute: absolutePathToTestDirectory, - // Drop the repository name from the rlocationpath. - TestDataPathRelative: path[strings.IndexRune(path, '/')+1:], + TestDataPathRelative: relativePathToTestDirectory, GazelleBinaryPath: absoluteGazelleBinary, BuildInSuffix: *buildInSuffix, BuildOutSuffix: *buildOutSuffix, From 7e267a164b7a1c9650e7fc2096707aebd60328f0 Mon Sep 17 00:00:00 2001 From: Marcel Date: Fri, 7 Feb 2025 15:02:42 +0100 Subject: [PATCH 03/27] Add more k8s.io default overrides (#2026) **What type of PR is this?** Other **What package or component does this PR mostly affect?** cmd/gazelle **What does this PR do? Why is it needed?** Proto generation must be disabled as generated proto bindings are already available in the repo. **Which issues(s) does this PR fix?** **Other notes for review** --- internal/bzlmod/default_gazelle_overrides.bzl | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/internal/bzlmod/default_gazelle_overrides.bzl b/internal/bzlmod/default_gazelle_overrides.bzl index e59a49bee..588dda0f5 100644 --- a/internal/bzlmod/default_gazelle_overrides.bzl +++ b/internal/bzlmod/default_gazelle_overrides.bzl @@ -108,6 +108,12 @@ DEFAULT_DIRECTIVES_BY_PATH = { "gazelle:go_generate_proto false", "gazelle:proto_import_prefix k8s.io/apimachinery", ], + "k8s.io/kubernetes": [ + "gazelle:proto disable", + ], + "k8s.io/metrics": [ + "gazelle:proto disable", + ], "storj.io/common": [ "gazelle:proto legacy", ], From 227f6ac789d494e48634e848518dfaa79c22df22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Fax=C3=A5?= Date: Fri, 7 Feb 2025 21:21:46 +0100 Subject: [PATCH 04/27] Add build file generation override for cncf/xds (#2027) **What type of PR is this?** Other **What package or component does this PR mostly affect?** internal/bzlmod **What does this PR do? Why is it needed?** Enable build file generation on a subpath to work around https://github.com/cncf/xds/issues/104. **Which issues(s) does this PR fix?** **Other notes for review** --- internal/bzlmod/default_gazelle_overrides.bzl | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/bzlmod/default_gazelle_overrides.bzl b/internal/bzlmod/default_gazelle_overrides.bzl index 588dda0f5..2f4b9dbba 100644 --- a/internal/bzlmod/default_gazelle_overrides.bzl +++ b/internal/bzlmod/default_gazelle_overrides.bzl @@ -15,6 +15,7 @@ visibility("private") DEFAULT_BUILD_FILE_GENERATION_BY_PATH = { + "github.com/cncf/xds/go": "on", "github.com/envoyproxy/protoc-gen-validate": "on", "github.com/google/safetext": "on", "github.com/grpc-ecosystem/grpc-gateway/v2": "on", From 99af6b381f814a11f2042c1324c016aa136ea31f Mon Sep 17 00:00:00 2001 From: Jay Conrod Date: Sat, 8 Feb 2025 01:40:24 -0800 Subject: [PATCH 05/27] Sync vendor directory (#2028) **What type of PR is this?** > Bug fix **What package or component does this PR mostly affect?** > vendor **What does this PR do? Why is it needed?** I keep getting editor errors that the vendor directory is out of sync with go.mod. This PR should fix that. go mod vendor find vendor -name BUILD.bazel -delete **Which issues(s) does this PR fix?** n/a **Other notes for review** --- .../buildtools/build/build_defs.bzl | 10 +- .../github.com/fsnotify/fsnotify/.cirrus.yml | 7 +- .../fsnotify/fsnotify/.editorconfig | 12 + .../fsnotify/fsnotify/.gitattributes | 1 + .../github.com/fsnotify/fsnotify/.gitignore | 3 - .../github.com/fsnotify/fsnotify/CHANGELOG.md | 34 +- .../fsnotify/fsnotify/CONTRIBUTING.md | 120 +-- .../fsnotify/fsnotify/backend_fen.go | 324 ++++++-- .../fsnotify/fsnotify/backend_inotify.go | 594 +++++++------- .../fsnotify/fsnotify/backend_kqueue.go | 747 ++++++++++-------- .../fsnotify/fsnotify/backend_other.go | 204 ++++- .../fsnotify/fsnotify/backend_windows.go | 305 +++++-- .../github.com/fsnotify/fsnotify/fsnotify.go | 368 +-------- .../fsnotify/fsnotify/internal/darwin.go | 39 - .../fsnotify/internal/debug_darwin.go | 57 -- .../fsnotify/internal/debug_dragonfly.go | 33 - .../fsnotify/internal/debug_freebsd.go | 42 - .../fsnotify/internal/debug_kqueue.go | 32 - .../fsnotify/fsnotify/internal/debug_linux.go | 56 -- .../fsnotify/internal/debug_netbsd.go | 25 - .../fsnotify/internal/debug_openbsd.go | 28 - .../fsnotify/internal/debug_solaris.go | 45 -- .../fsnotify/internal/debug_windows.go | 40 - .../fsnotify/fsnotify/internal/freebsd.go | 31 - .../fsnotify/fsnotify/internal/internal.go | 2 - .../fsnotify/fsnotify/internal/unix.go | 31 - .../fsnotify/fsnotify/internal/unix2.go | 7 - .../fsnotify/fsnotify/internal/windows.go | 41 - vendor/github.com/fsnotify/fsnotify/mkdoc.zsh | 259 ++++++ .../fsnotify/fsnotify/system_bsd.go | 1 + .../fsnotify/fsnotify/system_darwin.go | 1 + vendor/golang.org/x/sys/unix/README.md | 2 +- vendor/golang.org/x/sys/unix/mkerrors.sh | 4 +- vendor/golang.org/x/sys/unix/syscall_aix.go | 2 +- vendor/golang.org/x/sys/unix/syscall_linux.go | 63 +- .../x/sys/unix/syscall_linux_arm64.go | 2 - .../x/sys/unix/syscall_linux_loong64.go | 2 - .../x/sys/unix/syscall_linux_riscv64.go | 2 - .../golang.org/x/sys/unix/vgetrandom_linux.go | 13 - .../x/sys/unix/vgetrandom_unsupported.go | 11 - vendor/golang.org/x/sys/unix/zerrors_linux.go | 13 +- .../x/sys/unix/zerrors_linux_386.go | 5 - .../x/sys/unix/zerrors_linux_amd64.go | 5 - .../x/sys/unix/zerrors_linux_arm.go | 5 - .../x/sys/unix/zerrors_linux_arm64.go | 5 - .../x/sys/unix/zerrors_linux_loong64.go | 5 - .../x/sys/unix/zerrors_linux_mips.go | 5 - .../x/sys/unix/zerrors_linux_mips64.go | 5 - .../x/sys/unix/zerrors_linux_mips64le.go | 5 - .../x/sys/unix/zerrors_linux_mipsle.go | 5 - .../x/sys/unix/zerrors_linux_ppc.go | 5 - .../x/sys/unix/zerrors_linux_ppc64.go | 5 - .../x/sys/unix/zerrors_linux_ppc64le.go | 5 - .../x/sys/unix/zerrors_linux_riscv64.go | 5 - .../x/sys/unix/zerrors_linux_s390x.go | 5 - .../x/sys/unix/zerrors_linux_sparc64.go | 5 - .../golang.org/x/sys/unix/zsyscall_linux.go | 17 + .../x/sys/unix/zsysnum_linux_amd64.go | 1 - .../x/sys/unix/zsysnum_linux_arm64.go | 2 +- .../x/sys/unix/zsysnum_linux_loong64.go | 2 - .../x/sys/unix/zsysnum_linux_riscv64.go | 2 +- vendor/golang.org/x/sys/unix/ztypes_linux.go | 88 +-- .../golang.org/x/sys/windows/dll_windows.go | 2 +- vendor/modules.txt | 9 +- 64 files changed, 1675 insertions(+), 2136 deletions(-) create mode 100644 vendor/github.com/fsnotify/fsnotify/.editorconfig create mode 100644 vendor/github.com/fsnotify/fsnotify/.gitattributes delete mode 100644 vendor/github.com/fsnotify/fsnotify/internal/darwin.go delete mode 100644 vendor/github.com/fsnotify/fsnotify/internal/debug_darwin.go delete mode 100644 vendor/github.com/fsnotify/fsnotify/internal/debug_dragonfly.go delete mode 100644 vendor/github.com/fsnotify/fsnotify/internal/debug_freebsd.go delete mode 100644 vendor/github.com/fsnotify/fsnotify/internal/debug_kqueue.go delete mode 100644 vendor/github.com/fsnotify/fsnotify/internal/debug_linux.go delete mode 100644 vendor/github.com/fsnotify/fsnotify/internal/debug_netbsd.go delete mode 100644 vendor/github.com/fsnotify/fsnotify/internal/debug_openbsd.go delete mode 100644 vendor/github.com/fsnotify/fsnotify/internal/debug_solaris.go delete mode 100644 vendor/github.com/fsnotify/fsnotify/internal/debug_windows.go delete mode 100644 vendor/github.com/fsnotify/fsnotify/internal/freebsd.go delete mode 100644 vendor/github.com/fsnotify/fsnotify/internal/internal.go delete mode 100644 vendor/github.com/fsnotify/fsnotify/internal/unix.go delete mode 100644 vendor/github.com/fsnotify/fsnotify/internal/unix2.go delete mode 100644 vendor/github.com/fsnotify/fsnotify/internal/windows.go create mode 100644 vendor/github.com/fsnotify/fsnotify/mkdoc.zsh delete mode 100644 vendor/golang.org/x/sys/unix/vgetrandom_linux.go delete mode 100644 vendor/golang.org/x/sys/unix/vgetrandom_unsupported.go diff --git a/vendor/github.com/bazelbuild/buildtools/build/build_defs.bzl b/vendor/github.com/bazelbuild/buildtools/build/build_defs.bzl index f7842dddd..04462607e 100644 --- a/vendor/github.com/bazelbuild/buildtools/build/build_defs.bzl +++ b/vendor/github.com/bazelbuild/buildtools/build/build_defs.bzl @@ -19,12 +19,6 @@ load( "@io_bazel_rules_go//go:def.bzl", "GoSource", ) -load("@rules_shell//shell:sh_binary.bzl", - "sh_binary" -) -load("@rules_shell//shell:sh_test.bzl", - "sh_test" -) _GO_YACC_TOOL = "@org_golang_x_tools//cmd/goyacc" @@ -136,7 +130,7 @@ diff -q "$$F1" "$$F2" eof """, ) - sh_test( + native.sh_test( name = src + "_checkshtest", size = "small", srcs = [src + "_check.sh"], @@ -153,7 +147,7 @@ eof cmd = "echo 'cp $${BUILD_WORKSPACE_DIRECTORY}/$(location " + gen + ") $${BUILD_WORKSPACE_DIRECTORY}/" + native.package_name() + "/" + src + "' > $@", ) - sh_binary( + native.sh_binary( name = src + "_copy", srcs = [src + "_copysh"], data = [gen], diff --git a/vendor/github.com/fsnotify/fsnotify/.cirrus.yml b/vendor/github.com/fsnotify/fsnotify/.cirrus.yml index f4e7dbf37..ffc7b992b 100644 --- a/vendor/github.com/fsnotify/fsnotify/.cirrus.yml +++ b/vendor/github.com/fsnotify/fsnotify/.cirrus.yml @@ -1,7 +1,7 @@ freebsd_task: name: 'FreeBSD' freebsd_instance: - image_family: freebsd-14-1 + image_family: freebsd-13-2 install_script: - pkg update -f - pkg install -y go @@ -9,6 +9,5 @@ freebsd_task: # run tests as user "cirrus" instead of root - pw useradd cirrus -m - chown -R cirrus:cirrus . - - FSNOTIFY_BUFFER=4096 sudo --preserve-env=FSNOTIFY_BUFFER -u cirrus go test -parallel 1 -race ./... - - sudo --preserve-env=FSNOTIFY_BUFFER -u cirrus go test -parallel 1 -race ./... - - FSNOTIFY_DEBUG=1 sudo --preserve-env=FSNOTIFY_BUFFER -u cirrus go test -parallel 1 -race -v ./... + - FSNOTIFY_BUFFER=4096 sudo --preserve-env=FSNOTIFY_BUFFER -u cirrus go test -parallel 1 -race ./... + - sudo --preserve-env=FSNOTIFY_BUFFER -u cirrus go test -parallel 1 -race ./... diff --git a/vendor/github.com/fsnotify/fsnotify/.editorconfig b/vendor/github.com/fsnotify/fsnotify/.editorconfig new file mode 100644 index 000000000..fad895851 --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[*.go] +indent_style = tab +indent_size = 4 +insert_final_newline = true + +[*.{yml,yaml}] +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true diff --git a/vendor/github.com/fsnotify/fsnotify/.gitattributes b/vendor/github.com/fsnotify/fsnotify/.gitattributes new file mode 100644 index 000000000..32f1001be --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/.gitattributes @@ -0,0 +1 @@ +go.sum linguist-generated diff --git a/vendor/github.com/fsnotify/fsnotify/.gitignore b/vendor/github.com/fsnotify/fsnotify/.gitignore index daea9dd6d..391cc076b 100644 --- a/vendor/github.com/fsnotify/fsnotify/.gitignore +++ b/vendor/github.com/fsnotify/fsnotify/.gitignore @@ -5,6 +5,3 @@ # Output of go build ./cmd/fsnotify /fsnotify /fsnotify.exe - -/test/kqueue -/test/a.out diff --git a/vendor/github.com/fsnotify/fsnotify/CHANGELOG.md b/vendor/github.com/fsnotify/fsnotify/CHANGELOG.md index fa854785d..e0e575754 100644 --- a/vendor/github.com/fsnotify/fsnotify/CHANGELOG.md +++ b/vendor/github.com/fsnotify/fsnotify/CHANGELOG.md @@ -1,36 +1,8 @@ # Changelog -1.8.0 2023-10-31 ----------------- - -### Additions - -- all: add `FSNOTIFY_DEBUG` to print debug logs to stderr ([#619]) - -### Changes and fixes - -- windows: fix behaviour of `WatchList()` to be consistent with other platforms ([#610]) - -- kqueue: ignore events with Ident=0 ([#590]) - -- kqueue: set O_CLOEXEC to prevent passing file descriptors to children ([#617]) - -- kqueue: emit events as "/path/dir/file" instead of "path/link/file" when watching a symlink ([#625]) - -- inotify: don't send event for IN_DELETE_SELF when also watching the parent ([#620]) - -- inotify: fix panic when calling Remove() in a goroutine ([#650]) - -- fen: allow watching subdirectories of watched directories ([#621]) - -[#590]: https://github.com/fsnotify/fsnotify/pull/590 -[#610]: https://github.com/fsnotify/fsnotify/pull/610 -[#617]: https://github.com/fsnotify/fsnotify/pull/617 -[#619]: https://github.com/fsnotify/fsnotify/pull/619 -[#620]: https://github.com/fsnotify/fsnotify/pull/620 -[#621]: https://github.com/fsnotify/fsnotify/pull/621 -[#625]: https://github.com/fsnotify/fsnotify/pull/625 -[#650]: https://github.com/fsnotify/fsnotify/pull/650 +Unreleased +---------- +Nothing yet. 1.7.0 - 2023-10-22 ------------------ diff --git a/vendor/github.com/fsnotify/fsnotify/CONTRIBUTING.md b/vendor/github.com/fsnotify/fsnotify/CONTRIBUTING.md index e4ac2a2ff..ea379759d 100644 --- a/vendor/github.com/fsnotify/fsnotify/CONTRIBUTING.md +++ b/vendor/github.com/fsnotify/fsnotify/CONTRIBUTING.md @@ -1,7 +1,7 @@ Thank you for your interest in contributing to fsnotify! We try to review and merge PRs in a reasonable timeframe, but please be aware that: -- To avoid "wasted" work, please discuss changes on the issue tracker first. You +- To avoid "wasted" work, please discus changes on the issue tracker first. You can just send PRs, but they may end up being rejected for one reason or the other. @@ -20,124 +20,6 @@ platforms. Testing different platforms locally can be done with something like Use the `-short` flag to make the "stress test" run faster. -Writing new tests ------------------ -Scripts in the testdata directory allow creating test cases in a "shell-like" -syntax. The basic format is: - - script - - Output: - desired output - -For example: - - # Create a new empty file with some data. - watch / - echo data >/file - - Output: - create /file - write /file - -Just create a new file to add a new test; select which tests to run with -`-run TestScript/[path]`. - -script ------- -The script is a "shell-like" script: - - cmd arg arg - -Comments are supported with `#`: - - # Comment - cmd arg arg # Comment - -All operations are done in a temp directory; a path like "/foo" is rewritten to -"/tmp/TestFoo/foo". - -Arguments can be quoted with `"` or `'`; there are no escapes and they're -functionally identical right now, but this may change in the future, so best to -assume shell-like rules. - - touch "/file with spaces" - -End-of-line escapes with `\` are not supported. - -### Supported commands - - watch path [ops] # Watch the path, reporting events for it. Nothing is - # watched by default. Optionally a list of ops can be - # given, as with AddWith(path, WithOps(...)). - unwatch path # Stop watching the path. - watchlist n # Assert watchlist length. - - stop # Stop running the script; for debugging. - debug [yes/no] # Enable/disable FSNOTIFY_DEBUG (tests are run in - parallel by default, so -parallel=1 is probably a good - idea). - - touch path - mkdir [-p] dir - ln -s target link # Only ln -s supported. - mkfifo path - mknod dev path - mv src dst - rm [-r] path - chmod mode path # Octal only - sleep time-in-ms - - cat path # Read path (does nothing with the data; just reads it). - echo str >>path # Append "str" to "path". - echo str >path # Truncate "path" and write "str". - - require reason # Skip the test if "reason" is true; "skip" and - skip reason # "require" behave identical; it supports both for - # readability. Possible reasons are: - # - # always Always skip this test. - # symlink Symlinks are supported (requires admin - # permissions on Windows). - # mkfifo Platform doesn't support FIFO named sockets. - # mknod Platform doesn't support device nodes. - - -output ------- -After `Output:` the desired output is given; this is indented by convention, but -that's not required. - -The format of that is: - - # Comment - event path # Comment - - system: - event path - system2: - event path - -Every event is one line, and any whitespace between the event and path are -ignored. The path can optionally be surrounded in ". Anything after a "#" is -ignored. - -Platform-specific tests can be added after GOOS; for example: - - watch / - touch /file - - Output: - # Tested if nothing else matches - create /file - - # Windows-specific test. - windows: - write /file - -You can specify multiple platforms with a comma (e.g. "windows, linux:"). -"kqueue" is a shortcut for all kqueue systems (BSD, macOS). - [goon]: https://github.com/arp242/goon [Vagrant]: https://www.vagrantup.com/ diff --git a/vendor/github.com/fsnotify/fsnotify/backend_fen.go b/vendor/github.com/fsnotify/fsnotify/backend_fen.go index c349c326c..28497f1dd 100644 --- a/vendor/github.com/fsnotify/fsnotify/backend_fen.go +++ b/vendor/github.com/fsnotify/fsnotify/backend_fen.go @@ -1,8 +1,8 @@ //go:build solaris +// +build solaris -// FEN backend for illumos (supported) and Solaris (untested, but should work). -// -// See port_create(3c) etc. for docs. https://www.illumos.org/man/3C/port_create +// Note: the documentation on the Watcher type and methods is generated from +// mkdoc.zsh package fsnotify @@ -12,33 +12,150 @@ import ( "os" "path/filepath" "sync" - "time" - "github.com/fsnotify/fsnotify/internal" "golang.org/x/sys/unix" ) -type fen 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. +// +// # Windows notes +// +// Paths can be added as "C:\path\to\dir", but forward slashes +// ("C:/path/to/dir") will also work. +// +// When a watched directory is removed it will always send an event for the +// directory itself, but may not send events for all files in that directory. +// Sometimes it will send events for all times, sometimes it will send no +// events, and often only for some files. +// +// The default ReadDirectoryChangesW() buffer size is 64K, which is the largest +// value that is guaranteed to work with SMB filesystems. If you have many +// events in quick succession this may not be enough, and you will have to use +// [WithBufferSize] to increase the value. +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, and you may + // want to wait until you've stopped receiving them + // (see the dedup example in cmd/fsnotify). + // + // Some systems may send Write event for directories + // when the directory content changes. + // + // 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 + // when a file is truncated. On Windows it's never + // sent. Events chan Event + + // Errors sends any errors. + // + // ErrEventOverflow is used to indicate there are too many events: + // + // - inotify: There are too many queued events (fs.inotify.max_queued_events sysctl) + // - windows: The buffer size is too small; WithBufferSize() can be used to increase it. + // - kqueue, fen: Not used. Errors chan error mu sync.Mutex port *unix.EventPort - done chan struct{} // Channel for sending a "quit message" to the reader goroutine - dirs map[string]Op // Explicitly watched directories - watches map[string]Op // Explicitly watched non-directories + done chan struct{} // Channel for sending a "quit message" to the reader goroutine + dirs map[string]struct{} // Explicitly watched directories + watches map[string]struct{} // Explicitly watched non-directories } -func newBackend(ev chan Event, errs chan error) (backend, error) { - return newBufferedBackend(0, ev, errs) +// NewWatcher creates a new Watcher. +func NewWatcher() (*Watcher, error) { + return NewBufferedWatcher(0) } -func newBufferedBackend(sz uint, ev chan Event, errs chan error) (backend, error) { - w := &fen{ - Events: ev, - Errors: errs, - dirs: make(map[string]Op), - watches: make(map[string]Op), +// NewBufferedWatcher creates a new Watcher with a buffered Watcher.Events +// channel. +// +// The main use case for this is situations with a very large number of events +// where the kernel buffer size can't be increased (e.g. due to lack of +// permissions). An unbuffered Watcher will perform better for almost all use +// cases, and whenever possible you will be better off increasing the kernel +// buffers instead of adding a large userspace buffer. +func NewBufferedWatcher(sz uint) (*Watcher, error) { + w := &Watcher{ + Events: make(chan Event, sz), + Errors: make(chan error), + dirs: make(map[string]struct{}), + watches: make(map[string]struct{}), done: make(chan struct{}), } @@ -54,30 +171,27 @@ func newBufferedBackend(sz uint, ev chan Event, errs chan error) (backend, error // sendEvent attempts to send an event to the user, returning true if the event // was put in the channel successfully and false if the watcher has been closed. -func (w *fen) sendEvent(name string, op Op) (sent bool) { +func (w *Watcher) sendEvent(name string, op Op) (sent bool) { select { - case <-w.done: - return false case w.Events <- Event{Name: name, Op: op}: return true + case <-w.done: + return false } } // sendError attempts to send an error to the user, returning true if the error // was put in the channel successfully and false if the watcher has been closed. -func (w *fen) sendError(err error) (sent bool) { - if err == nil { - return true - } +func (w *Watcher) sendError(err error) (sent bool) { select { - case <-w.done: - return false case w.Errors <- err: return true + case <-w.done: + return false } } -func (w *fen) isClosed() bool { +func (w *Watcher) isClosed() bool { select { case <-w.done: return true @@ -86,7 +200,8 @@ func (w *fen) isClosed() bool { } } -func (w *fen) Close() error { +// Close removes all watches and closes the Events channel. +func (w *Watcher) Close() error { // Take the lock used by associateFile to prevent lingering events from // being processed after the close w.mu.Lock() @@ -98,21 +213,60 @@ func (w *fen) Close() error { return w.port.Close() } -func (w *fen) Add(name string) error { return w.AddWith(name) } +// Add starts monitoring the path for changes. +// +// A path can only be watched once; watching it more than once is a no-op and will +// not return an error. Paths that do not yet exist on the filesystem cannot be +// watched. +// +// A watch will be automatically removed if the watched path is deleted or +// renamed. The exception is the Windows backend, which doesn't remove the +// watcher on renames. +// +// Notifications on network filesystems (NFS, SMB, FUSE, etc.) or special +// filesystems (/proc, /sys, etc.) generally don't work. +// +// Returns [ErrClosed] if [Watcher.Close] was called. +// +// See [Watcher.AddWith] for a version that allows adding options. +// +// # Watching directories +// +// All files in a directory are monitored, including new files that are created +// after the watcher is started. Subdirectories are not watched (i.e. it's +// non-recursive). +// +// # Watching files +// +// Watching individual files (rather than directories) is generally not +// recommended as many programs (especially editors) update files atomically: it +// will write to a temporary file which is then moved to to destination, +// overwriting the original (or some variant thereof). The watcher on the +// original file is now lost, as that no longer exists. +// +// The upshot of this is that a power failure or crash won't leave a +// half-written file. +// +// Watch the parent directory and use Event.Name to filter out files you're not +// interested in. There is an example of this in cmd/fsnotify/file.go. +func (w *Watcher) Add(name string) error { return w.AddWith(name) } -func (w *fen) AddWith(name string, opts ...addOpt) error { +// AddWith is like [Watcher.Add], but allows adding options. When using Add() +// the defaults described below are used. +// +// Possible options are: +// +// - [WithBufferSize] sets the buffer size for the Windows backend; no-op on +// other platforms. The default is 64K (65536 bytes). +func (w *Watcher) AddWith(name string, opts ...addOpt) error { if w.isClosed() { return ErrClosed } - if debug { - fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s AddWith(%q)\n", - time.Now().Format("15:04:05.000000000"), name) + if w.port.PathIsWatched(name) { + return nil } - with := getOptions(opts...) - if !w.xSupports(with.op) { - return fmt.Errorf("%w: %s", xErrUnsupported, with.op) - } + _ = getOptions(opts...) // Currently we resolve symlinks that were explicitly requested to be // watched. Otherwise we would use LStat here. @@ -129,7 +283,7 @@ func (w *fen) AddWith(name string, opts ...addOpt) error { } w.mu.Lock() - w.dirs[name] = with.op + w.dirs[name] = struct{}{} w.mu.Unlock() return nil } @@ -140,22 +294,26 @@ func (w *fen) AddWith(name string, opts ...addOpt) error { } w.mu.Lock() - w.watches[name] = with.op + w.watches[name] = struct{}{} w.mu.Unlock() return nil } -func (w *fen) Remove(name string) error { +// Remove stops monitoring the path for changes. +// +// Directories are always removed non-recursively. For example, if you added +// /tmp/dir and /tmp/dir/subdir then you will need to remove both. +// +// Removing a path that has not yet been added returns [ErrNonExistentWatch]. +// +// Returns nil if [Watcher.Close] was called. +func (w *Watcher) Remove(name string) error { if w.isClosed() { return nil } if !w.port.PathIsWatched(name) { return fmt.Errorf("%w: %s", ErrNonExistentWatch, name) } - if debug { - fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s Remove(%q)\n", - time.Now().Format("15:04:05.000000000"), name) - } // The user has expressed an intent. Immediately remove this name from // whichever watch list it might be in. If it's not in there the delete @@ -188,7 +346,7 @@ func (w *fen) Remove(name string) error { } // readEvents contains the main loop that runs in a goroutine watching for events. -func (w *fen) readEvents() { +func (w *Watcher) readEvents() { // If this function returns, the watcher has been closed and we can close // these channels defer func() { @@ -224,19 +382,17 @@ func (w *fen) readEvents() { continue } - if debug { - internal.Debug(pevent.Path, pevent.Events) - } - err = w.handleEvent(&pevent) - if !w.sendError(err) { - return + if err != nil { + if !w.sendError(err) { + return + } } } } } -func (w *fen) handleDirectory(path string, stat os.FileInfo, follow bool, handler func(string, os.FileInfo, bool) error) error { +func (w *Watcher) handleDirectory(path string, stat os.FileInfo, follow bool, handler func(string, os.FileInfo, bool) error) error { files, err := os.ReadDir(path) if err != nil { return err @@ -262,7 +418,7 @@ func (w *fen) handleDirectory(path string, stat os.FileInfo, follow bool, handle // bitmap matches more than one event type (e.g. the file was both modified and // had the attributes changed between when the association was created and the // when event was returned) -func (w *fen) handleEvent(event *unix.PortEvent) error { +func (w *Watcher) handleEvent(event *unix.PortEvent) error { var ( events = event.Events path = event.Path @@ -354,9 +510,15 @@ func (w *fen) handleEvent(event *unix.PortEvent) error { } if events&unix.FILE_MODIFIED != 0 { - if fmode.IsDir() && watchedDir { - if err := w.updateDirectory(path); err != nil { - return err + if fmode.IsDir() { + if watchedDir { + if err := w.updateDirectory(path); err != nil { + return err + } + } else { + if !w.sendEvent(path, Write) { + return nil + } } } else { if !w.sendEvent(path, Write) { @@ -381,7 +543,7 @@ func (w *fen) handleEvent(event *unix.PortEvent) error { return nil } -func (w *fen) updateDirectory(path string) error { +func (w *Watcher) updateDirectory(path string) error { // The directory was modified, so we must find unwatched entities and watch // them. If something was removed from the directory, nothing will happen, // as everything else should still be watched. @@ -401,8 +563,10 @@ func (w *fen) updateDirectory(path string) error { return err } err = w.associateFile(path, finfo, false) - if !w.sendError(err) { - return nil + if err != nil { + if !w.sendError(err) { + return nil + } } if !w.sendEvent(path, Create) { return nil @@ -411,7 +575,7 @@ func (w *fen) updateDirectory(path string) error { return nil } -func (w *fen) associateFile(path string, stat os.FileInfo, follow bool) error { +func (w *Watcher) associateFile(path string, stat os.FileInfo, follow bool) error { if w.isClosed() { return ErrClosed } @@ -429,34 +593,34 @@ func (w *fen) associateFile(path string, stat os.FileInfo, follow bool) error { // cleared up that discrepancy. The most likely cause is that the event // has fired but we haven't processed it yet. err := w.port.DissociatePath(path) - if err != nil && !errors.Is(err, unix.ENOENT) { + if err != nil && err != unix.ENOENT { return err } } - - var events int - if !follow { - // Watch symlinks themselves rather than their targets unless this entry - // is explicitly watched. - events |= unix.FILE_NOFOLLOW - } - if true { // TODO: implement withOps() - events |= unix.FILE_MODIFIED + // FILE_NOFOLLOW means we watch symlinks themselves rather than their + // targets. + events := unix.FILE_MODIFIED | unix.FILE_ATTRIB | unix.FILE_NOFOLLOW + if follow { + // We *DO* follow symlinks for explicitly watched entries. + events = unix.FILE_MODIFIED | unix.FILE_ATTRIB } - if true { - events |= unix.FILE_ATTRIB - } - return w.port.AssociatePath(path, stat, events, stat.Mode()) + return w.port.AssociatePath(path, stat, + events, + stat.Mode()) } -func (w *fen) dissociateFile(path string, stat os.FileInfo, unused bool) error { +func (w *Watcher) dissociateFile(path string, stat os.FileInfo, unused bool) error { if !w.port.PathIsWatched(path) { return nil } return w.port.DissociatePath(path) } -func (w *fen) WatchList() []string { +// WatchList returns all paths explicitly added with [Watcher.Add] (and are not +// yet removed). +// +// Returns nil if [Watcher.Close] was called. +func (w *Watcher) WatchList() []string { if w.isClosed() { return nil } @@ -474,11 +638,3 @@ func (w *fen) WatchList() []string { return entries } - -func (w *fen) xSupports(op Op) bool { - if op.Has(xUnportableOpen) || op.Has(xUnportableRead) || - op.Has(xUnportableCloseWrite) || op.Has(xUnportableCloseRead) { - return false - } - return true -} diff --git a/vendor/github.com/fsnotify/fsnotify/backend_inotify.go b/vendor/github.com/fsnotify/fsnotify/backend_inotify.go index 36c311694..921c1c1e4 100644 --- a/vendor/github.com/fsnotify/fsnotify/backend_inotify.go +++ b/vendor/github.com/fsnotify/fsnotify/backend_inotify.go @@ -1,4 +1,8 @@ //go:build linux && !appengine +// +build linux,!appengine + +// Note: the documentation on the Watcher type and methods is generated from +// mkdoc.zsh package fsnotify @@ -6,20 +10,127 @@ import ( "errors" "fmt" "io" - "io/fs" "os" "path/filepath" "strings" "sync" - "time" "unsafe" - "github.com/fsnotify/fsnotify/internal" "golang.org/x/sys/unix" ) -type inotify 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. +// +// # Windows notes +// +// Paths can be added as "C:\path\to\dir", but forward slashes +// ("C:/path/to/dir") will also work. +// +// When a watched directory is removed it will always send an event for the +// directory itself, but may not send events for all files in that directory. +// Sometimes it will send events for all times, sometimes it will send no +// events, and often only for some files. +// +// The default ReadDirectoryChangesW() buffer size is 64K, which is the largest +// value that is guaranteed to work with SMB filesystems. If you have many +// events in quick succession this may not be enough, and you will have to use +// [WithBufferSize] to increase the value. +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, and you may + // want to wait until you've stopped receiving them + // (see the dedup example in cmd/fsnotify). + // + // Some systems may send Write event for directories + // when the directory content changes. + // + // 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 + // when a file is truncated. On Windows it's never + // sent. Events chan Event + + // Errors sends any errors. + // + // ErrEventOverflow is used to indicate there are too many events: + // + // - inotify: There are too many queued events (fs.inotify.max_queued_events sysctl) + // - windows: The buffer size is too small; WithBufferSize() can be used to increase it. + // - kqueue, fen: Not used. Errors chan error // Store fd here as os.File.Read() will no longer return on close after @@ -28,26 +139,8 @@ type inotify struct { inotifyFile *os.File watches *watches done chan struct{} // Channel for sending a "quit message" to the reader goroutine - doneMu sync.Mutex + closeMu sync.Mutex doneResp chan struct{} // Channel to respond to Close - - // Store rename cookies in an array, with the index wrapping to 0. Almost - // all of the time what we get is a MOVED_FROM to set the cookie and the - // next event inotify sends will be MOVED_TO to read it. However, this is - // not guaranteed – as described in inotify(7) – and we may get other events - // between the two MOVED_* events (including other MOVED_* ones). - // - // A second issue is that moving a file outside the watched directory will - // trigger a MOVED_FROM to set the cookie, but we never see the MOVED_TO to - // read and delete it. So just storing it in a map would slowly leak memory. - // - // Doing it like this gives us a simple fast LRU-cache that won't allocate. - // Ten items should be more than enough for our purpose, and a loop over - // such a short array is faster than a map access anyway (not that it hugely - // matters since we're talking about hundreds of ns at the most, but still). - cookies [10]koekje - cookieIndex uint8 - cookiesMu sync.Mutex } type ( @@ -57,14 +150,9 @@ type ( path map[string]uint32 // pathname → wd } watch struct { - wd uint32 // Watch descriptor (as returned by the inotify_add_watch() syscall) - flags uint32 // inotify flags of this watch (see inotify(7) for the list of valid flags) - path string // Watch path. - recurse bool // Recursion with ./...? - } - koekje struct { - cookie uint32 - path string + wd uint32 // Watch descriptor (as returned by the inotify_add_watch() syscall) + flags uint32 // inotify flags of this watch (see inotify(7) for the list of valid flags) + path string // Watch path. } ) @@ -91,45 +179,23 @@ func (w *watches) add(ww *watch) { func (w *watches) remove(wd uint32) { w.mu.Lock() defer w.mu.Unlock() - watch := w.wd[wd] // Could have had Remove() called. See #616. - if watch == nil { - return - } - delete(w.path, watch.path) + delete(w.path, w.wd[wd].path) delete(w.wd, wd) } -func (w *watches) removePath(path string) ([]uint32, error) { +func (w *watches) removePath(path string) (uint32, bool) { w.mu.Lock() defer w.mu.Unlock() - path, recurse := recursivePath(path) wd, ok := w.path[path] if !ok { - return nil, fmt.Errorf("%w: %s", ErrNonExistentWatch, path) - } - - watch := w.wd[wd] - if recurse && !watch.recurse { - return nil, fmt.Errorf("can't use /... with non-recursive watch %q", path) + return 0, false } delete(w.path, path) delete(w.wd, wd) - if !watch.recurse { - return []uint32{wd}, nil - } - wds := make([]uint32, 0, 8) - wds = append(wds, wd) - for p, rwd := range w.path { - if filepath.HasPrefix(p, path) { - delete(w.path, p) - delete(w.wd, rwd) - wds = append(wds, rwd) - } - } - return wds, nil + return wd, true } func (w *watches) byPath(path string) *watch { @@ -170,11 +236,20 @@ func (w *watches) updatePath(path string, f func(*watch) (*watch, error)) error return nil } -func newBackend(ev chan Event, errs chan error) (backend, error) { - return newBufferedBackend(0, ev, errs) +// NewWatcher creates a new Watcher. +func NewWatcher() (*Watcher, error) { + return NewBufferedWatcher(0) } -func newBufferedBackend(sz uint, ev chan Event, errs chan error) (backend, error) { +// NewBufferedWatcher creates a new Watcher with a buffered Watcher.Events +// channel. +// +// The main use case for this is situations with a very large number of events +// where the kernel buffer size can't be increased (e.g. due to lack of +// permissions). An unbuffered Watcher will perform better for almost all use +// cases, and whenever possible you will be better off increasing the kernel +// buffers instead of adding a large userspace buffer. +func NewBufferedWatcher(sz uint) (*Watcher, error) { // Need to set nonblocking mode for SetDeadline to work, otherwise blocking // I/O operations won't terminate on close. fd, errno := unix.InotifyInit1(unix.IN_CLOEXEC | unix.IN_NONBLOCK) @@ -182,12 +257,12 @@ func newBufferedBackend(sz uint, ev chan Event, errs chan error) (backend, error return nil, errno } - w := &inotify{ - Events: ev, - Errors: errs, + w := &Watcher{ fd: fd, inotifyFile: os.NewFile(uintptr(fd), ""), watches: newWatches(), + Events: make(chan Event, sz), + Errors: make(chan error), done: make(chan struct{}), doneResp: make(chan struct{}), } @@ -197,29 +272,26 @@ func newBufferedBackend(sz uint, ev chan Event, errs chan error) (backend, error } // Returns true if the event was sent, or false if watcher is closed. -func (w *inotify) sendEvent(e Event) bool { +func (w *Watcher) sendEvent(e Event) bool { select { - case <-w.done: - return false case w.Events <- e: return true + case <-w.done: + return false } } // Returns true if the error was sent, or false if watcher is closed. -func (w *inotify) sendError(err error) bool { - if err == nil { - return true - } +func (w *Watcher) sendError(err error) bool { select { - case <-w.done: - return false case w.Errors <- err: return true + case <-w.done: + return false } } -func (w *inotify) isClosed() bool { +func (w *Watcher) isClosed() bool { select { case <-w.done: return true @@ -228,14 +300,15 @@ func (w *inotify) isClosed() bool { } } -func (w *inotify) Close() error { - w.doneMu.Lock() +// Close removes all watches and closes the Events channel. +func (w *Watcher) Close() error { + w.closeMu.Lock() if w.isClosed() { - w.doneMu.Unlock() + w.closeMu.Unlock() return nil } close(w.done) - w.doneMu.Unlock() + w.closeMu.Unlock() // Causes any blocking reads to return with an error, provided the file // still supports deadline operations. @@ -250,104 +323,78 @@ func (w *inotify) Close() error { return nil } -func (w *inotify) Add(name string) error { return w.AddWith(name) } - -func (w *inotify) AddWith(path string, opts ...addOpt) error { +// Add starts monitoring the path for changes. +// +// A path can only be watched once; watching it more than once is a no-op and will +// not return an error. Paths that do not yet exist on the filesystem cannot be +// watched. +// +// A watch will be automatically removed if the watched path is deleted or +// renamed. The exception is the Windows backend, which doesn't remove the +// watcher on renames. +// +// Notifications on network filesystems (NFS, SMB, FUSE, etc.) or special +// filesystems (/proc, /sys, etc.) generally don't work. +// +// Returns [ErrClosed] if [Watcher.Close] was called. +// +// See [Watcher.AddWith] for a version that allows adding options. +// +// # Watching directories +// +// All files in a directory are monitored, including new files that are created +// after the watcher is started. Subdirectories are not watched (i.e. it's +// non-recursive). +// +// # Watching files +// +// Watching individual files (rather than directories) is generally not +// recommended as many programs (especially editors) update files atomically: it +// will write to a temporary file which is then moved to to destination, +// overwriting the original (or some variant thereof). The watcher on the +// original file is now lost, as that no longer exists. +// +// The upshot of this is that a power failure or crash won't leave a +// half-written file. +// +// Watch the parent directory and use Event.Name to filter out files you're not +// interested in. There is an example of this in cmd/fsnotify/file.go. +func (w *Watcher) Add(name string) error { return w.AddWith(name) } + +// AddWith is like [Watcher.Add], but allows adding options. When using Add() +// the defaults described below are used. +// +// Possible options are: +// +// - [WithBufferSize] sets the buffer size for the Windows backend; no-op on +// other platforms. The default is 64K (65536 bytes). +func (w *Watcher) AddWith(name string, opts ...addOpt) error { if w.isClosed() { return ErrClosed } - if debug { - fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s AddWith(%q)\n", - time.Now().Format("15:04:05.000000000"), path) - } - - with := getOptions(opts...) - if !w.xSupports(with.op) { - return fmt.Errorf("%w: %s", xErrUnsupported, with.op) - } - path, recurse := recursivePath(path) - if recurse { - return filepath.WalkDir(path, func(root string, d fs.DirEntry, err error) error { - if err != nil { - return err - } - if !d.IsDir() { - if root == path { - return fmt.Errorf("fsnotify: not a directory: %q", path) - } - return nil - } + name = filepath.Clean(name) + _ = getOptions(opts...) - // Send a Create event when adding new directory from a recursive - // watch; this is for "mkdir -p one/two/three". Usually all those - // directories will be created before we can set up watchers on the - // subdirectories, so only "one" would be sent as a Create event and - // not "one/two" and "one/two/three" (inotifywait -r has the same - // problem). - if with.sendCreate && root != path { - w.sendEvent(Event{Name: root, Op: Create}) - } - - return w.add(root, with, true) - }) - } + var flags uint32 = unix.IN_MOVED_TO | unix.IN_MOVED_FROM | + unix.IN_CREATE | unix.IN_ATTRIB | unix.IN_MODIFY | + unix.IN_MOVE_SELF | unix.IN_DELETE | unix.IN_DELETE_SELF - return w.add(path, with, false) -} - -func (w *inotify) add(path string, with withOpts, recurse bool) error { - var flags uint32 - if with.noFollow { - flags |= unix.IN_DONT_FOLLOW - } - if with.op.Has(Create) { - flags |= unix.IN_CREATE - } - if with.op.Has(Write) { - flags |= unix.IN_MODIFY - } - if with.op.Has(Remove) { - flags |= unix.IN_DELETE | unix.IN_DELETE_SELF - } - if with.op.Has(Rename) { - flags |= unix.IN_MOVED_TO | unix.IN_MOVED_FROM | unix.IN_MOVE_SELF - } - if with.op.Has(Chmod) { - flags |= unix.IN_ATTRIB - } - if with.op.Has(xUnportableOpen) { - flags |= unix.IN_OPEN - } - if with.op.Has(xUnportableRead) { - flags |= unix.IN_ACCESS - } - if with.op.Has(xUnportableCloseWrite) { - flags |= unix.IN_CLOSE_WRITE - } - if with.op.Has(xUnportableCloseRead) { - flags |= unix.IN_CLOSE_NOWRITE - } - return w.register(path, flags, recurse) -} - -func (w *inotify) register(path string, flags uint32, recurse bool) error { - return w.watches.updatePath(path, func(existing *watch) (*watch, error) { + return w.watches.updatePath(name, func(existing *watch) (*watch, error) { if existing != nil { flags |= existing.flags | unix.IN_MASK_ADD } - wd, err := unix.InotifyAddWatch(w.fd, path, flags) + wd, err := unix.InotifyAddWatch(w.fd, name, flags) if wd == -1 { return nil, err } if existing == nil { return &watch{ - wd: uint32(wd), - path: path, - flags: flags, - recurse: recurse, + wd: uint32(wd), + path: name, + flags: flags, }, nil } @@ -357,44 +404,49 @@ func (w *inotify) register(path string, flags uint32, recurse bool) error { }) } -func (w *inotify) Remove(name string) error { +// Remove stops monitoring the path for changes. +// +// Directories are always removed non-recursively. For example, if you added +// /tmp/dir and /tmp/dir/subdir then you will need to remove both. +// +// Removing a path that has not yet been added returns [ErrNonExistentWatch]. +// +// Returns nil if [Watcher.Close] was called. +func (w *Watcher) Remove(name string) error { if w.isClosed() { return nil } - if debug { - fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s Remove(%q)\n", - time.Now().Format("15:04:05.000000000"), name) - } return w.remove(filepath.Clean(name)) } -func (w *inotify) remove(name string) error { - wds, err := w.watches.removePath(name) - if err != nil { - return err - } - - for _, wd := range wds { - _, err := unix.InotifyRmWatch(w.fd, wd) - if err != nil { - // TODO: Perhaps it's not helpful to return an error here in every - // case; the only two possible errors are: - // - // EBADF, which happens when w.fd is not a valid file descriptor of - // any kind. - // - // EINVAL, which is when fd is not an inotify descriptor or wd is - // not a valid watch descriptor. Watch descriptors are invalidated - // when they are removed explicitly or implicitly; explicitly by - // inotify_rm_watch, implicitly when the file they are watching is - // deleted. - return err - } +func (w *Watcher) remove(name string) error { + wd, ok := w.watches.removePath(name) + if !ok { + return fmt.Errorf("%w: %s", ErrNonExistentWatch, name) + } + + success, errno := unix.InotifyRmWatch(w.fd, wd) + if success == -1 { + // TODO: Perhaps it's not helpful to return an error here in every case; + // The only two possible errors are: + // + // - EBADF, which happens when w.fd is not a valid file descriptor + // of any kind. + // - EINVAL, which is when fd is not an inotify descriptor or wd + // is not a valid watch descriptor. Watch descriptors are + // invalidated when they are removed explicitly or implicitly; + // explicitly by inotify_rm_watch, implicitly when the file they + // are watching is deleted. + return errno } return nil } -func (w *inotify) WatchList() []string { +// WatchList returns all paths explicitly added with [Watcher.Add] (and are not +// yet removed). +// +// Returns nil if [Watcher.Close] was called. +func (w *Watcher) WatchList() []string { if w.isClosed() { return nil } @@ -411,7 +463,7 @@ func (w *inotify) WatchList() []string { // readEvents reads from the inotify file descriptor, converts the // received events into Event objects and sends them via the Events channel -func (w *inotify) readEvents() { +func (w *Watcher) readEvents() { defer func() { close(w.doneResp) close(w.Errors) @@ -454,17 +506,15 @@ func (w *inotify) readEvents() { continue } + var offset uint32 // We don't know how many events we just read into the buffer // While the offset points to at least one whole event... - var offset uint32 for offset <= uint32(n-unix.SizeofInotifyEvent) { var ( // Point "raw" to the event in the buffer raw = (*unix.InotifyEvent)(unsafe.Pointer(&buf[offset])) mask = uint32(raw.Mask) nameLen = uint32(raw.Len) - // Move to the next event in the buffer - next = func() { offset += unix.SizeofInotifyEvent + nameLen } ) if mask&unix.IN_Q_OVERFLOW != 0 { @@ -473,53 +523,21 @@ func (w *inotify) readEvents() { } } - /// If the event happened to the watched directory or the watched - /// file, the kernel doesn't append the filename to the event, but - /// we would like to always fill the the "Name" field with a valid - /// filename. We retrieve the path of the watch from the "paths" - /// map. + // If the event happened to the watched directory or the watched file, the kernel + // doesn't append the filename to the event, but we would like to always fill the + // the "Name" field with a valid filename. We retrieve the path of the watch from + // the "paths" map. watch := w.watches.byWd(uint32(raw.Wd)) - /// Can be nil if Remove() was called in another goroutine for this - /// path inbetween reading the events from the kernel and reading - /// the internal state. Not much we can do about it, so just skip. - /// See #616. - if watch == nil { - next() - continue - } - - name := watch.path - if nameLen > 0 { - /// Point "bytes" at the first byte of the filename - bytes := (*[unix.PathMax]byte)(unsafe.Pointer(&buf[offset+unix.SizeofInotifyEvent]))[:nameLen:nameLen] - /// The filename is padded with NULL bytes. TrimRight() gets rid of those. - name += "/" + strings.TrimRight(string(bytes[0:nameLen]), "\000") - } - - if debug { - internal.Debug(name, raw.Mask, raw.Cookie) - } - - if mask&unix.IN_IGNORED != 0 { //&& event.Op != 0 - next() - continue - } // inotify will automatically remove the watch on deletes; just need // to clean our state here. - if mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF { + if watch != nil && mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF { w.watches.remove(watch.wd) } - // We can't really update the state when a watched path is moved; // only IN_MOVE_SELF is sent and not IN_MOVED_{FROM,TO}. So remove // the watch. - if mask&unix.IN_MOVE_SELF == unix.IN_MOVE_SELF { - if watch.recurse { - next() // Do nothing - continue - } - + if watch != nil && mask&unix.IN_MOVE_SELF == unix.IN_MOVE_SELF { err := w.remove(watch.path) if err != nil && !errors.Is(err, ErrNonExistentWatch) { if !w.sendError(err) { @@ -528,69 +546,34 @@ func (w *inotify) readEvents() { } } - /// Skip if we're watching both this path and the parent; the parent - /// will already send a delete so no need to do it twice. - if mask&unix.IN_DELETE_SELF != 0 { - if _, ok := w.watches.path[filepath.Dir(watch.path)]; ok { - next() - continue - } + var name string + if watch != nil { + name = watch.path + } + if nameLen > 0 { + // Point "bytes" at the first byte of the filename + bytes := (*[unix.PathMax]byte)(unsafe.Pointer(&buf[offset+unix.SizeofInotifyEvent]))[:nameLen:nameLen] + // The filename is padded with NULL bytes. TrimRight() gets rid of those. + name += "/" + strings.TrimRight(string(bytes[0:nameLen]), "\000") } - ev := w.newEvent(name, mask, raw.Cookie) - // Need to update watch path for recurse. - if watch.recurse { - isDir := mask&unix.IN_ISDIR == unix.IN_ISDIR - /// New directory created: set up watch on it. - if isDir && ev.Has(Create) { - err := w.register(ev.Name, watch.flags, true) - if !w.sendError(err) { - return - } + event := w.newEvent(name, mask) - // This was a directory rename, so we need to update all - // the children. - // - // TODO: this is of course pretty slow; we should use a - // better data structure for storing all of this, e.g. store - // children in the watch. I have some code for this in my - // kqueue refactor we can use in the future. For now I'm - // okay with this as it's not publicly available. - // Correctness first, performance second. - if ev.renamedFrom != "" { - w.watches.mu.Lock() - for k, ww := range w.watches.wd { - if k == watch.wd || ww.path == ev.Name { - continue - } - if strings.HasPrefix(ww.path, ev.renamedFrom) { - ww.path = strings.Replace(ww.path, ev.renamedFrom, ev.Name, 1) - w.watches.wd[k] = ww - } - } - w.watches.mu.Unlock() - } + // Send the events that are not ignored on the events channel + if mask&unix.IN_IGNORED == 0 { + if !w.sendEvent(event) { + return } } - /// Send the events that are not ignored on the events channel - if !w.sendEvent(ev) { - return - } - next() + // Move to the next event in the buffer + offset += unix.SizeofInotifyEvent + nameLen } } } -func (w *inotify) isRecursive(path string) bool { - ww := w.watches.byPath(path) - if ww == nil { // path could be a file, so also check the Dir. - ww = w.watches.byPath(filepath.Dir(path)) - } - return ww != nil && ww.recurse -} - -func (w *inotify) newEvent(name string, mask, cookie uint32) Event { +// newEvent returns an platform-independent Event based on an inotify mask. +func (w *Watcher) newEvent(name string, mask uint32) Event { e := Event{Name: name} if mask&unix.IN_CREATE == unix.IN_CREATE || mask&unix.IN_MOVED_TO == unix.IN_MOVED_TO { e.Op |= Create @@ -601,58 +584,11 @@ func (w *inotify) newEvent(name string, mask, cookie uint32) Event { if mask&unix.IN_MODIFY == unix.IN_MODIFY { e.Op |= Write } - if mask&unix.IN_OPEN == unix.IN_OPEN { - e.Op |= xUnportableOpen - } - if mask&unix.IN_ACCESS == unix.IN_ACCESS { - e.Op |= xUnportableRead - } - if mask&unix.IN_CLOSE_WRITE == unix.IN_CLOSE_WRITE { - e.Op |= xUnportableCloseWrite - } - if mask&unix.IN_CLOSE_NOWRITE == unix.IN_CLOSE_NOWRITE { - e.Op |= xUnportableCloseRead - } if mask&unix.IN_MOVE_SELF == unix.IN_MOVE_SELF || mask&unix.IN_MOVED_FROM == unix.IN_MOVED_FROM { e.Op |= Rename } if mask&unix.IN_ATTRIB == unix.IN_ATTRIB { e.Op |= Chmod } - - if cookie != 0 { - if mask&unix.IN_MOVED_FROM == unix.IN_MOVED_FROM { - w.cookiesMu.Lock() - w.cookies[w.cookieIndex] = koekje{cookie: cookie, path: e.Name} - w.cookieIndex++ - if w.cookieIndex > 9 { - w.cookieIndex = 0 - } - w.cookiesMu.Unlock() - } else if mask&unix.IN_MOVED_TO == unix.IN_MOVED_TO { - w.cookiesMu.Lock() - var prev string - for _, c := range w.cookies { - if c.cookie == cookie { - prev = c.path - break - } - } - w.cookiesMu.Unlock() - e.renamedFrom = prev - } - } return e } - -func (w *inotify) xSupports(op Op) bool { - return true // Supports everything. -} - -func (w *inotify) state() { - w.watches.mu.Lock() - defer w.watches.mu.Unlock() - for wd, ww := range w.watches.wd { - fmt.Fprintf(os.Stderr, "%4d: recurse=%t %q\n", wd, ww.recurse, ww.path) - } -} diff --git a/vendor/github.com/fsnotify/fsnotify/backend_kqueue.go b/vendor/github.com/fsnotify/fsnotify/backend_kqueue.go index d8de5ab76..063a0915a 100644 --- a/vendor/github.com/fsnotify/fsnotify/backend_kqueue.go +++ b/vendor/github.com/fsnotify/fsnotify/backend_kqueue.go @@ -1,4 +1,8 @@ //go:build freebsd || openbsd || netbsd || dragonfly || darwin +// +build freebsd openbsd netbsd dragonfly darwin + +// Note: the documentation on the Watcher type and methods is generated from +// mkdoc.zsh package fsnotify @@ -7,195 +11,174 @@ import ( "fmt" "os" "path/filepath" - "runtime" "sync" - "time" - "github.com/fsnotify/fsnotify/internal" "golang.org/x/sys/unix" ) -type kqueue 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. +// +// # Windows notes +// +// Paths can be added as "C:\path\to\dir", but forward slashes +// ("C:/path/to/dir") will also work. +// +// When a watched directory is removed it will always send an event for the +// directory itself, but may not send events for all files in that directory. +// Sometimes it will send events for all times, sometimes it will send no +// events, and often only for some files. +// +// The default ReadDirectoryChangesW() buffer size is 64K, which is the largest +// value that is guaranteed to work with SMB filesystems. If you have many +// events in quick succession this may not be enough, and you will have to use +// [WithBufferSize] to increase the value. +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, and you may + // want to wait until you've stopped receiving them + // (see the dedup example in cmd/fsnotify). + // + // Some systems may send Write event for directories + // when the directory content changes. + // + // 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 + // when a file is truncated. On Windows it's never + // sent. Events chan Event - Errors chan error - - kq int // File descriptor (as returned by the kqueue() syscall). - closepipe [2]int // Pipe used for closing kq. - watches *watches - done chan struct{} - doneMu sync.Mutex -} - -type ( - watches struct { - mu sync.RWMutex - wd map[int]watch // wd → watch - path map[string]int // pathname → wd - byDir map[string]map[int]struct{} // dirname(path) → wd - seen map[string]struct{} // Keep track of if we know this file exists. - byUser map[string]struct{} // Watches added with Watcher.Add() - } - watch struct { - wd int - name string - linkName string // In case of links; name is the target, and this is the link. - isDir bool - dirFlags uint32 - } -) - -func newWatches() *watches { - return &watches{ - wd: make(map[int]watch), - path: make(map[string]int), - byDir: make(map[string]map[int]struct{}), - seen: make(map[string]struct{}), - byUser: make(map[string]struct{}), - } -} - -func (w *watches) listPaths(userOnly bool) []string { - w.mu.RLock() - defer w.mu.RUnlock() - - if userOnly { - l := make([]string, 0, len(w.byUser)) - for p := range w.byUser { - l = append(l, p) - } - return l - } - - l := make([]string, 0, len(w.path)) - for p := range w.path { - l = append(l, p) - } - return l -} - -func (w *watches) watchesInDir(path string) []string { - w.mu.RLock() - defer w.mu.RUnlock() - - l := make([]string, 0, 4) - for fd := range w.byDir[path] { - info := w.wd[fd] - if _, ok := w.byUser[info.name]; !ok { - l = append(l, info.name) - } - } - return l -} - -// Mark path as added by the user. -func (w *watches) addUserWatch(path string) { - w.mu.Lock() - defer w.mu.Unlock() - w.byUser[path] = struct{}{} -} - -func (w *watches) addLink(path string, fd int) { - w.mu.Lock() - defer w.mu.Unlock() - - w.path[path] = fd - w.seen[path] = struct{}{} -} - -func (w *watches) add(path, linkPath string, fd int, isDir bool) { - w.mu.Lock() - defer w.mu.Unlock() - - w.path[path] = fd - w.wd[fd] = watch{wd: fd, name: path, linkName: linkPath, isDir: isDir} - - parent := filepath.Dir(path) - byDir, ok := w.byDir[parent] - if !ok { - byDir = make(map[int]struct{}, 1) - w.byDir[parent] = byDir - } - byDir[fd] = struct{}{} -} - -func (w *watches) byWd(fd int) (watch, bool) { - w.mu.RLock() - defer w.mu.RUnlock() - info, ok := w.wd[fd] - return info, ok -} - -func (w *watches) byPath(path string) (watch, bool) { - w.mu.RLock() - defer w.mu.RUnlock() - info, ok := w.wd[w.path[path]] - return info, ok -} - -func (w *watches) updateDirFlags(path string, flags uint32) { - w.mu.Lock() - defer w.mu.Unlock() - - fd := w.path[path] - info := w.wd[fd] - info.dirFlags = flags - w.wd[fd] = info -} - -func (w *watches) remove(fd int, path string) bool { - w.mu.Lock() - defer w.mu.Unlock() - - isDir := w.wd[fd].isDir - delete(w.path, path) - delete(w.byUser, path) - - parent := filepath.Dir(path) - delete(w.byDir[parent], fd) - - if len(w.byDir[parent]) == 0 { - delete(w.byDir, parent) - } - delete(w.wd, fd) - delete(w.seen, path) - return isDir -} + // Errors sends any errors. + // + // ErrEventOverflow is used to indicate there are too many events: + // + // - inotify: There are too many queued events (fs.inotify.max_queued_events sysctl) + // - windows: The buffer size is too small; WithBufferSize() can be used to increase it. + // - kqueue, fen: Not used. + Errors chan error -func (w *watches) markSeen(path string, exists bool) { - w.mu.Lock() - defer w.mu.Unlock() - if exists { - w.seen[path] = struct{}{} - } else { - delete(w.seen, path) - } + done chan struct{} + kq int // File descriptor (as returned by the kqueue() syscall). + closepipe [2]int // Pipe used for closing. + mu sync.Mutex // Protects access to watcher data + watches map[string]int // Watched file descriptors (key: path). + watchesByDir map[string]map[int]struct{} // Watched file descriptors indexed by the parent directory (key: dirname(path)). + userWatches map[string]struct{} // Watches added with Watcher.Add() + dirFlags map[string]uint32 // Watched directories to fflags used in kqueue. + paths map[int]pathInfo // File descriptors to path names for processing kqueue events. + fileExists map[string]struct{} // Keep track of if we know this file exists (to stop duplicate create events). + isClosed bool // Set to true when Close() is first called } -func (w *watches) seenBefore(path string) bool { - w.mu.RLock() - defer w.mu.RUnlock() - _, ok := w.seen[path] - return ok +type pathInfo struct { + name string + isDir bool } -func newBackend(ev chan Event, errs chan error) (backend, error) { - return newBufferedBackend(0, ev, errs) +// NewWatcher creates a new Watcher. +func NewWatcher() (*Watcher, error) { + return NewBufferedWatcher(0) } -func newBufferedBackend(sz uint, ev chan Event, errs chan error) (backend, error) { +// NewBufferedWatcher creates a new Watcher with a buffered Watcher.Events +// channel. +// +// The main use case for this is situations with a very large number of events +// where the kernel buffer size can't be increased (e.g. due to lack of +// permissions). An unbuffered Watcher will perform better for almost all use +// cases, and whenever possible you will be better off increasing the kernel +// buffers instead of adding a large userspace buffer. +func NewBufferedWatcher(sz uint) (*Watcher, error) { kq, closepipe, err := newKqueue() if err != nil { return nil, err } - w := &kqueue{ - Events: ev, - Errors: errs, - kq: kq, - closepipe: closepipe, - done: make(chan struct{}), - watches: newWatches(), + w := &Watcher{ + kq: kq, + closepipe: closepipe, + watches: make(map[string]int), + watchesByDir: make(map[string]map[int]struct{}), + dirFlags: make(map[string]uint32), + paths: make(map[int]pathInfo), + fileExists: make(map[string]struct{}), + userWatches: make(map[string]struct{}), + Events: make(chan Event, sz), + Errors: make(chan error), + done: make(chan struct{}), } go w.readEvents() @@ -220,8 +203,6 @@ func newKqueue() (kq int, closepipe [2]int, err error) { unix.Close(kq) return kq, closepipe, err } - unix.CloseOnExec(closepipe[0]) - unix.CloseOnExec(closepipe[1]) // Register changes to listen on the closepipe. changes := make([]unix.Kevent_t, 1) @@ -240,108 +221,166 @@ func newKqueue() (kq int, closepipe [2]int, err error) { } // Returns true if the event was sent, or false if watcher is closed. -func (w *kqueue) sendEvent(e Event) bool { +func (w *Watcher) sendEvent(e Event) bool { select { - case <-w.done: - return false case w.Events <- e: return true + case <-w.done: + return false } } // Returns true if the error was sent, or false if watcher is closed. -func (w *kqueue) sendError(err error) bool { - if err == nil { - return true - } +func (w *Watcher) sendError(err error) bool { select { - case <-w.done: - return false case w.Errors <- err: return true - } -} - -func (w *kqueue) isClosed() bool { - select { case <-w.done: - return true - default: return false } } -func (w *kqueue) Close() error { - w.doneMu.Lock() - if w.isClosed() { - w.doneMu.Unlock() +// Close removes all watches and closes the Events channel. +func (w *Watcher) Close() error { + w.mu.Lock() + if w.isClosed { + w.mu.Unlock() return nil } - close(w.done) - w.doneMu.Unlock() + w.isClosed = true - pathsToRemove := w.watches.listPaths(false) + // copy paths to remove while locked + pathsToRemove := make([]string, 0, len(w.watches)) + for name := range w.watches { + pathsToRemove = append(pathsToRemove, name) + } + w.mu.Unlock() // Unlock before calling Remove, which also locks for _, name := range pathsToRemove { w.Remove(name) } // Send "quit" message to the reader goroutine. unix.Close(w.closepipe[1]) + close(w.done) + return nil } -func (w *kqueue) Add(name string) error { return w.AddWith(name) } - -func (w *kqueue) AddWith(name string, opts ...addOpt) error { - if debug { - fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s AddWith(%q)\n", - time.Now().Format("15:04:05.000000000"), name) - } +// Add starts monitoring the path for changes. +// +// A path can only be watched once; watching it more than once is a no-op and will +// not return an error. Paths that do not yet exist on the filesystem cannot be +// watched. +// +// A watch will be automatically removed if the watched path is deleted or +// renamed. The exception is the Windows backend, which doesn't remove the +// watcher on renames. +// +// Notifications on network filesystems (NFS, SMB, FUSE, etc.) or special +// filesystems (/proc, /sys, etc.) generally don't work. +// +// Returns [ErrClosed] if [Watcher.Close] was called. +// +// See [Watcher.AddWith] for a version that allows adding options. +// +// # Watching directories +// +// All files in a directory are monitored, including new files that are created +// after the watcher is started. Subdirectories are not watched (i.e. it's +// non-recursive). +// +// # Watching files +// +// Watching individual files (rather than directories) is generally not +// recommended as many programs (especially editors) update files atomically: it +// will write to a temporary file which is then moved to to destination, +// overwriting the original (or some variant thereof). The watcher on the +// original file is now lost, as that no longer exists. +// +// The upshot of this is that a power failure or crash won't leave a +// half-written file. +// +// Watch the parent directory and use Event.Name to filter out files you're not +// interested in. There is an example of this in cmd/fsnotify/file.go. +func (w *Watcher) Add(name string) error { return w.AddWith(name) } - with := getOptions(opts...) - if !w.xSupports(with.op) { - return fmt.Errorf("%w: %s", xErrUnsupported, with.op) - } +// AddWith is like [Watcher.Add], but allows adding options. When using Add() +// the defaults described below are used. +// +// Possible options are: +// +// - [WithBufferSize] sets the buffer size for the Windows backend; no-op on +// other platforms. The default is 64K (65536 bytes). +func (w *Watcher) AddWith(name string, opts ...addOpt) error { + _ = getOptions(opts...) + w.mu.Lock() + w.userWatches[name] = struct{}{} + w.mu.Unlock() _, err := w.addWatch(name, noteAllEvents) - if err != nil { - return err - } - w.watches.addUserWatch(name) - return nil + return err } -func (w *kqueue) Remove(name string) error { - if debug { - fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s Remove(%q)\n", - time.Now().Format("15:04:05.000000000"), name) - } +// Remove stops monitoring the path for changes. +// +// Directories are always removed non-recursively. For example, if you added +// /tmp/dir and /tmp/dir/subdir then you will need to remove both. +// +// Removing a path that has not yet been added returns [ErrNonExistentWatch]. +// +// Returns nil if [Watcher.Close] was called. +func (w *Watcher) Remove(name string) error { return w.remove(name, true) } -func (w *kqueue) remove(name string, unwatchFiles bool) error { - if w.isClosed() { +func (w *Watcher) remove(name string, unwatchFiles bool) error { + name = filepath.Clean(name) + w.mu.Lock() + if w.isClosed { + w.mu.Unlock() return nil } - - name = filepath.Clean(name) - info, ok := w.watches.byPath(name) + watchfd, ok := w.watches[name] + w.mu.Unlock() if !ok { return fmt.Errorf("%w: %s", ErrNonExistentWatch, name) } - err := w.register([]int{info.wd}, unix.EV_DELETE, 0) + err := w.register([]int{watchfd}, unix.EV_DELETE, 0) if err != nil { return err } - unix.Close(info.wd) + unix.Close(watchfd) + + w.mu.Lock() + isDir := w.paths[watchfd].isDir + delete(w.watches, name) + delete(w.userWatches, name) + + parentName := filepath.Dir(name) + delete(w.watchesByDir[parentName], watchfd) + + if len(w.watchesByDir[parentName]) == 0 { + delete(w.watchesByDir, parentName) + } - isDir := w.watches.remove(info.wd, name) + delete(w.paths, watchfd) + delete(w.dirFlags, name) + delete(w.fileExists, name) + w.mu.Unlock() // Find all watched paths that are in this directory that are not external. if unwatchFiles && isDir { - pathsToRemove := w.watches.watchesInDir(name) + var pathsToRemove []string + w.mu.Lock() + for fd := range w.watchesByDir[name] { + path := w.paths[fd] + if _, ok := w.userWatches[path.name]; !ok { + pathsToRemove = append(pathsToRemove, path.name) + } + } + w.mu.Unlock() for _, name := range pathsToRemove { // Since these are internal, not much sense in propagating error to // the user, as that will just confuse them with an error about a @@ -352,11 +391,23 @@ func (w *kqueue) remove(name string, unwatchFiles bool) error { return nil } -func (w *kqueue) WatchList() []string { - if w.isClosed() { +// WatchList returns all paths explicitly added with [Watcher.Add] (and are not +// yet removed). +// +// Returns nil if [Watcher.Close] was called. +func (w *Watcher) WatchList() []string { + w.mu.Lock() + defer w.mu.Unlock() + if w.isClosed { return nil } - return w.watches.listPaths(true) + + entries := make([]string, 0, len(w.userWatches)) + for pathname := range w.userWatches { + entries = append(entries, pathname) + } + + return entries } // Watch all events (except NOTE_EXTEND, NOTE_LINK, NOTE_REVOKE) @@ -366,26 +417,34 @@ const noteAllEvents = unix.NOTE_DELETE | unix.NOTE_WRITE | unix.NOTE_ATTRIB | un // described in kevent(2). // // Returns the real path to the file which was added, with symlinks resolved. -func (w *kqueue) addWatch(name string, flags uint32) (string, error) { - if w.isClosed() { +func (w *Watcher) addWatch(name string, flags uint32) (string, error) { + var isDir bool + name = filepath.Clean(name) + + w.mu.Lock() + if w.isClosed { + w.mu.Unlock() return "", ErrClosed } + watchfd, alreadyWatching := w.watches[name] + // We already have a watch, but we can still override flags. + if alreadyWatching { + isDir = w.paths[watchfd].isDir + } + w.mu.Unlock() - name = filepath.Clean(name) - - info, alreadyWatching := w.watches.byPath(name) if !alreadyWatching { fi, err := os.Lstat(name) if err != nil { return "", err } - // Don't watch sockets or named pipes. + // Don't watch sockets or named pipes if (fi.Mode()&os.ModeSocket == os.ModeSocket) || (fi.Mode()&os.ModeNamedPipe == os.ModeNamedPipe) { return "", nil } - // Follow symlinks. + // Follow Symlinks. if fi.Mode()&os.ModeSymlink == os.ModeSymlink { link, err := os.Readlink(name) if err != nil { @@ -396,15 +455,18 @@ func (w *kqueue) addWatch(name string, flags uint32) (string, error) { return "", nil } - _, alreadyWatching = w.watches.byPath(link) + w.mu.Lock() + _, alreadyWatching = w.watches[link] + w.mu.Unlock() + if alreadyWatching { // Add to watches so we don't get spurious Create events later // on when we diff the directories. - w.watches.addLink(name, 0) + w.watches[name] = 0 + w.fileExists[name] = struct{}{} return link, nil } - info.linkName = name name = link fi, err = os.Lstat(name) if err != nil { @@ -415,7 +477,7 @@ func (w *kqueue) addWatch(name string, flags uint32) (string, error) { // Retry on EINTR; open() can return EINTR in practice on macOS. // See #354, and Go issues 11180 and 39237. for { - info.wd, err = unix.Open(name, openMode, 0) + watchfd, err = unix.Open(name, openMode, 0) if err == nil { break } @@ -426,25 +488,40 @@ func (w *kqueue) addWatch(name string, flags uint32) (string, error) { return "", err } - info.isDir = fi.IsDir() + isDir = fi.IsDir() } - err := w.register([]int{info.wd}, unix.EV_ADD|unix.EV_CLEAR|unix.EV_ENABLE, flags) + err := w.register([]int{watchfd}, unix.EV_ADD|unix.EV_CLEAR|unix.EV_ENABLE, flags) if err != nil { - unix.Close(info.wd) + unix.Close(watchfd) return "", err } if !alreadyWatching { - w.watches.add(name, info.linkName, info.wd, info.isDir) + w.mu.Lock() + parentName := filepath.Dir(name) + w.watches[name] = watchfd + + watchesByDir, ok := w.watchesByDir[parentName] + if !ok { + watchesByDir = make(map[int]struct{}, 1) + w.watchesByDir[parentName] = watchesByDir + } + watchesByDir[watchfd] = struct{}{} + w.paths[watchfd] = pathInfo{name: name, isDir: isDir} + w.mu.Unlock() } - // Watch the directory if it has not been watched before, or if it was - // watched before, but perhaps only a NOTE_DELETE (watchDirectoryFiles) - if info.isDir { + if isDir { + // Watch the directory if it has not been watched before, or if it was + // watched before, but perhaps only a NOTE_DELETE (watchDirectoryFiles) + w.mu.Lock() + watchDir := (flags&unix.NOTE_WRITE) == unix.NOTE_WRITE && - (!alreadyWatching || (info.dirFlags&unix.NOTE_WRITE) != unix.NOTE_WRITE) - w.watches.updateDirFlags(name, flags) + (!alreadyWatching || (w.dirFlags[name]&unix.NOTE_WRITE) != unix.NOTE_WRITE) + // Store flags so this watch can be updated later + w.dirFlags[name] = flags + w.mu.Unlock() if watchDir { if err := w.watchDirectoryFiles(name); err != nil { @@ -457,7 +534,7 @@ func (w *kqueue) addWatch(name string, flags uint32) (string, error) { // readEvents reads from kqueue and converts the received kevents into // Event values that it sends down the Events channel. -func (w *kqueue) readEvents() { +func (w *Watcher) readEvents() { defer func() { close(w.Events) close(w.Errors) @@ -466,65 +543,50 @@ func (w *kqueue) readEvents() { }() eventBuffer := make([]unix.Kevent_t, 10) - for { + for closed := false; !closed; { kevents, err := w.read(eventBuffer) // EINTR is okay, the syscall was interrupted before timeout expired. if err != nil && err != unix.EINTR { if !w.sendError(fmt.Errorf("fsnotify.readEvents: %w", err)) { - return + closed = true } + continue } + // Flush the events we received to the Events channel for _, kevent := range kevents { var ( - wd = int(kevent.Ident) - mask = uint32(kevent.Fflags) + watchfd = int(kevent.Ident) + mask = uint32(kevent.Fflags) ) // Shut down the loop when the pipe is closed, but only after all // other events have been processed. - if wd == w.closepipe[0] { - return - } - - path, ok := w.watches.byWd(wd) - if debug { - internal.Debug(path.name, &kevent) - } - - // On macOS it seems that sometimes an event with Ident=0 is - // delivered, and no other flags/information beyond that, even - // though we never saw such a file descriptor. For example in - // TestWatchSymlink/277 (usually at the end, but sometimes sooner): - // - // fmt.Printf("READ: %2d %#v\n", kevent.Ident, kevent) - // unix.Kevent_t{Ident:0x2a, Filter:-4, Flags:0x25, Fflags:0x2, Data:0, Udata:(*uint8)(nil)} - // unix.Kevent_t{Ident:0x0, Filter:-4, Flags:0x25, Fflags:0x2, Data:0, Udata:(*uint8)(nil)} - // - // The first is a normal event, the second with Ident 0. No error - // flag, no data, no ... nothing. - // - // I read a bit through bsd/kern_event.c from the xnu source, but I - // don't really see an obvious location where this is triggered – - // this doesn't seem intentional, but idk... - // - // Technically fd 0 is a valid descriptor, so only skip it if - // there's no path, and if we're on macOS. - if !ok && kevent.Ident == 0 && runtime.GOOS == "darwin" { + if watchfd == w.closepipe[0] { + closed = true continue } - event := w.newEvent(path.name, path.linkName, mask) + w.mu.Lock() + path := w.paths[watchfd] + w.mu.Unlock() + + event := w.newEvent(path.name, mask) if event.Has(Rename) || event.Has(Remove) { w.remove(event.Name, false) - w.watches.markSeen(event.Name, false) + w.mu.Lock() + delete(w.fileExists, event.Name) + w.mu.Unlock() } if path.isDir && event.Has(Write) && !event.Has(Remove) { - w.dirChange(event.Name) - } else if !w.sendEvent(event) { - return + w.sendDirectoryChangeEvents(event.Name) + } else { + if !w.sendEvent(event) { + closed = true + continue + } } if event.Has(Remove) { @@ -532,34 +594,25 @@ func (w *kqueue) readEvents() { // mv f1 f2 will delete f2, then create f2. if path.isDir { fileDir := filepath.Clean(event.Name) - _, found := w.watches.byPath(fileDir) + w.mu.Lock() + _, found := w.watches[fileDir] + w.mu.Unlock() if found { - // TODO: this branch is never triggered in any test. - // Added in d6220df (2012). - // isDir check added in 8611c35 (2016): https://github.com/fsnotify/fsnotify/pull/111 - // - // I don't really get how this can be triggered either. - // And it wasn't triggered in the patch that added it, - // either. - // - // Original also had a comment: - // make sure the directory exists before we watch for - // changes. When we do a recursive watch and perform - // rm -rf, the parent directory might have gone - // missing, ignore the missing directory and let the - // upcoming delete event remove the watch from the - // parent directory. - err := w.dirChange(fileDir) - if !w.sendError(err) { - return + err := w.sendDirectoryChangeEvents(fileDir) + if err != nil { + if !w.sendError(err) { + closed = true + } } } } else { - path := filepath.Clean(event.Name) - if fi, err := os.Lstat(path); err == nil { - err := w.sendCreateIfNew(path, fi) - if !w.sendError(err) { - return + filePath := filepath.Clean(event.Name) + if fi, err := os.Lstat(filePath); err == nil { + err := w.sendFileCreatedEventIfNew(filePath, fi) + if err != nil { + if !w.sendError(err) { + closed = true + } } } } @@ -569,14 +622,8 @@ func (w *kqueue) readEvents() { } // newEvent returns an platform-independent Event based on kqueue Fflags. -func (w *kqueue) newEvent(name, linkName string, mask uint32) Event { +func (w *Watcher) newEvent(name string, mask uint32) Event { e := Event{Name: name} - if linkName != "" { - // If the user watched "/path/link" then emit events as "/path/link" - // rather than "/path/target". - e.Name = linkName - } - if mask&unix.NOTE_DELETE == unix.NOTE_DELETE { e.Op |= Remove } @@ -598,7 +645,8 @@ func (w *kqueue) newEvent(name, linkName string, mask uint32) Event { } // watchDirectoryFiles to mimic inotify when adding a watch on a directory -func (w *kqueue) watchDirectoryFiles(dirPath string) error { +func (w *Watcher) watchDirectoryFiles(dirPath string) error { + // Get all files files, err := os.ReadDir(dirPath) if err != nil { return err @@ -626,7 +674,9 @@ func (w *kqueue) watchDirectoryFiles(dirPath string) error { } } - w.watches.markSeen(cleanPath, true) + w.mu.Lock() + w.fileExists[cleanPath] = struct{}{} + w.mu.Unlock() } return nil @@ -636,7 +686,7 @@ func (w *kqueue) watchDirectoryFiles(dirPath string) error { // // This functionality is to have the BSD watcher match the inotify, which sends // a create event for files created in a watched directory. -func (w *kqueue) dirChange(dir string) error { +func (w *Watcher) sendDirectoryChangeEvents(dir string) error { files, err := os.ReadDir(dir) if err != nil { // Directory no longer exists: we can ignore this safely. kqueue will @@ -644,51 +694,61 @@ func (w *kqueue) dirChange(dir string) error { if errors.Is(err, os.ErrNotExist) { return nil } - return fmt.Errorf("fsnotify.dirChange: %w", err) + return fmt.Errorf("fsnotify.sendDirectoryChangeEvents: %w", err) } for _, f := range files { fi, err := f.Info() if err != nil { - return fmt.Errorf("fsnotify.dirChange: %w", err) + return fmt.Errorf("fsnotify.sendDirectoryChangeEvents: %w", err) } - err = w.sendCreateIfNew(filepath.Join(dir, fi.Name()), fi) + err = w.sendFileCreatedEventIfNew(filepath.Join(dir, fi.Name()), fi) if err != nil { // Don't need to send an error if this file isn't readable. if errors.Is(err, unix.EACCES) || errors.Is(err, unix.EPERM) { return nil } - return fmt.Errorf("fsnotify.dirChange: %w", err) + return fmt.Errorf("fsnotify.sendDirectoryChangeEvents: %w", err) } } return nil } -// Send a create event if the file isn't already being tracked, and start -// watching this file. -func (w *kqueue) sendCreateIfNew(path string, fi os.FileInfo) error { - if !w.watches.seenBefore(path) { - if !w.sendEvent(Event{Name: path, Op: Create}) { - return nil +// sendFileCreatedEvent sends a create event if the file isn't already being tracked. +func (w *Watcher) sendFileCreatedEventIfNew(filePath string, fi os.FileInfo) (err error) { + w.mu.Lock() + _, doesExist := w.fileExists[filePath] + w.mu.Unlock() + if !doesExist { + if !w.sendEvent(Event{Name: filePath, Op: Create}) { + return } } - // Like watchDirectoryFiles, but without doing another ReadDir. - path, err := w.internalWatch(path, fi) + // like watchDirectoryFiles (but without doing another ReadDir) + filePath, err = w.internalWatch(filePath, fi) if err != nil { return err } - w.watches.markSeen(path, true) + + w.mu.Lock() + w.fileExists[filePath] = struct{}{} + w.mu.Unlock() + return nil } -func (w *kqueue) internalWatch(name string, fi os.FileInfo) (string, error) { +func (w *Watcher) internalWatch(name string, fi os.FileInfo) (string, error) { if fi.IsDir() { // mimic Linux providing delete events for subdirectories, but preserve // the flags used if currently watching subdirectory - info, _ := w.watches.byPath(name) - return w.addWatch(name, info.dirFlags|unix.NOTE_DELETE|unix.NOTE_RENAME) + w.mu.Lock() + flags := w.dirFlags[name] + w.mu.Unlock() + + flags |= unix.NOTE_DELETE | unix.NOTE_RENAME + return w.addWatch(name, flags) } // watch file to mimic Linux inotify @@ -696,7 +756,7 @@ func (w *kqueue) internalWatch(name string, fi os.FileInfo) (string, error) { } // Register events with the queue. -func (w *kqueue) register(fds []int, flags int, fflags uint32) error { +func (w *Watcher) register(fds []int, flags int, fflags uint32) error { changes := make([]unix.Kevent_t, len(fds)) for i, fd := range fds { // SetKevent converts int to the platform-specific types. @@ -713,21 +773,10 @@ func (w *kqueue) register(fds []int, flags int, fflags uint32) error { } // read retrieves pending events, or waits until an event occurs. -func (w *kqueue) read(events []unix.Kevent_t) ([]unix.Kevent_t, error) { +func (w *Watcher) read(events []unix.Kevent_t) ([]unix.Kevent_t, error) { n, err := unix.Kevent(w.kq, nil, events, nil) if err != nil { return nil, err } return events[0:n], nil } - -func (w *kqueue) xSupports(op Op) bool { - if runtime.GOOS == "freebsd" { - //return true // Supports everything. - } - if op.Has(xUnportableOpen) || op.Has(xUnportableRead) || - op.Has(xUnportableCloseWrite) || op.Has(xUnportableCloseRead) { - return false - } - return true -} diff --git a/vendor/github.com/fsnotify/fsnotify/backend_other.go b/vendor/github.com/fsnotify/fsnotify/backend_other.go index 5eb5dbc66..d34a23c01 100644 --- a/vendor/github.com/fsnotify/fsnotify/backend_other.go +++ b/vendor/github.com/fsnotify/fsnotify/backend_other.go @@ -1,23 +1,205 @@ //go:build appengine || (!darwin && !dragonfly && !freebsd && !openbsd && !linux && !netbsd && !solaris && !windows) +// +build appengine !darwin,!dragonfly,!freebsd,!openbsd,!linux,!netbsd,!solaris,!windows + +// Note: the documentation on the Watcher type and methods is generated from +// mkdoc.zsh package fsnotify import "errors" -type other 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. +// +// # Windows notes +// +// Paths can be added as "C:\path\to\dir", but forward slashes +// ("C:/path/to/dir") will also work. +// +// When a watched directory is removed it will always send an event for the +// directory itself, but may not send events for all files in that directory. +// Sometimes it will send events for all times, sometimes it will send no +// events, and often only for some files. +// +// The default ReadDirectoryChangesW() buffer size is 64K, which is the largest +// value that is guaranteed to work with SMB filesystems. If you have many +// events in quick succession this may not be enough, and you will have to use +// [WithBufferSize] to increase the value. +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, and you may + // want to wait until you've stopped receiving them + // (see the dedup example in cmd/fsnotify). + // + // Some systems may send Write event for directories + // when the directory content changes. + // + // 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 + // when a file is truncated. On Windows it's never + // sent. Events chan Event + + // Errors sends any errors. + // + // ErrEventOverflow is used to indicate there are too many events: + // + // - inotify: There are too many queued events (fs.inotify.max_queued_events sysctl) + // - windows: The buffer size is too small; WithBufferSize() can be used to increase it. + // - kqueue, fen: Not used. Errors chan error } -func newBackend(ev chan Event, errs chan error) (backend, error) { +// NewWatcher creates a new Watcher. +func NewWatcher() (*Watcher, error) { return nil, errors.New("fsnotify not supported on the current platform") } -func newBufferedBackend(sz uint, ev chan Event, errs chan error) (backend, error) { - return newBackend(ev, errs) -} -func (w *other) Close() error { return nil } -func (w *other) WatchList() []string { return nil } -func (w *other) Add(name string) error { return nil } -func (w *other) AddWith(name string, opts ...addOpt) error { return nil } -func (w *other) Remove(name string) error { return nil } -func (w *other) xSupports(op Op) bool { return false } + +// NewBufferedWatcher creates a new Watcher with a buffered Watcher.Events +// channel. +// +// The main use case for this is situations with a very large number of events +// where the kernel buffer size can't be increased (e.g. due to lack of +// permissions). An unbuffered Watcher will perform better for almost all use +// cases, and whenever possible you will be better off increasing the kernel +// buffers instead of adding a large userspace buffer. +func NewBufferedWatcher(sz uint) (*Watcher, error) { return NewWatcher() } + +// Close removes all watches and closes the Events channel. +func (w *Watcher) Close() error { return nil } + +// WatchList returns all paths explicitly added with [Watcher.Add] (and are not +// yet removed). +// +// Returns nil if [Watcher.Close] was called. +func (w *Watcher) WatchList() []string { return nil } + +// Add starts monitoring the path for changes. +// +// A path can only be watched once; watching it more than once is a no-op and will +// not return an error. Paths that do not yet exist on the filesystem cannot be +// watched. +// +// A watch will be automatically removed if the watched path is deleted or +// renamed. The exception is the Windows backend, which doesn't remove the +// watcher on renames. +// +// Notifications on network filesystems (NFS, SMB, FUSE, etc.) or special +// filesystems (/proc, /sys, etc.) generally don't work. +// +// Returns [ErrClosed] if [Watcher.Close] was called. +// +// See [Watcher.AddWith] for a version that allows adding options. +// +// # Watching directories +// +// All files in a directory are monitored, including new files that are created +// after the watcher is started. Subdirectories are not watched (i.e. it's +// non-recursive). +// +// # Watching files +// +// Watching individual files (rather than directories) is generally not +// recommended as many programs (especially editors) update files atomically: it +// will write to a temporary file which is then moved to to destination, +// overwriting the original (or some variant thereof). The watcher on the +// original file is now lost, as that no longer exists. +// +// The upshot of this is that a power failure or crash won't leave a +// half-written file. +// +// Watch the parent directory and use Event.Name to filter out files you're not +// interested in. There is an example of this in cmd/fsnotify/file.go. +func (w *Watcher) Add(name string) error { return nil } + +// AddWith is like [Watcher.Add], but allows adding options. When using Add() +// the defaults described below are used. +// +// Possible options are: +// +// - [WithBufferSize] sets the buffer size for the Windows backend; no-op on +// other platforms. The default is 64K (65536 bytes). +func (w *Watcher) AddWith(name string, opts ...addOpt) error { return nil } + +// Remove stops monitoring the path for changes. +// +// Directories are always removed non-recursively. For example, if you added +// /tmp/dir and /tmp/dir/subdir then you will need to remove both. +// +// Removing a path that has not yet been added returns [ErrNonExistentWatch]. +// +// Returns nil if [Watcher.Close] was called. +func (w *Watcher) Remove(name string) error { return nil } diff --git a/vendor/github.com/fsnotify/fsnotify/backend_windows.go b/vendor/github.com/fsnotify/fsnotify/backend_windows.go index c54a63083..9bc91e5d6 100644 --- a/vendor/github.com/fsnotify/fsnotify/backend_windows.go +++ b/vendor/github.com/fsnotify/fsnotify/backend_windows.go @@ -1,8 +1,12 @@ //go:build windows +// +build windows // Windows backend based on ReadDirectoryChangesW() // // https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-readdirectorychangesw +// +// Note: the documentation on the Watcher type and methods is generated from +// mkdoc.zsh package fsnotify @@ -15,15 +19,123 @@ import ( "runtime" "strings" "sync" - "time" "unsafe" - "github.com/fsnotify/fsnotify/internal" "golang.org/x/sys/windows" ) -type readDirChangesW 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. +// +// # Windows notes +// +// Paths can be added as "C:\path\to\dir", but forward slashes +// ("C:/path/to/dir") will also work. +// +// When a watched directory is removed it will always send an event for the +// directory itself, but may not send events for all files in that directory. +// Sometimes it will send events for all times, sometimes it will send no +// events, and often only for some files. +// +// The default ReadDirectoryChangesW() buffer size is 64K, which is the largest +// value that is guaranteed to work with SMB filesystems. If you have many +// events in quick succession this may not be enough, and you will have to use +// [WithBufferSize] to increase the value. +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, and you may + // want to wait until you've stopped receiving them + // (see the dedup example in cmd/fsnotify). + // + // Some systems may send Write event for directories + // when the directory content changes. + // + // 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 + // when a file is truncated. On Windows it's never + // sent. Events chan Event + + // Errors sends any errors. + // + // ErrEventOverflow is used to indicate there are too many events: + // + // - inotify: There are too many queued events (fs.inotify.max_queued_events sysctl) + // - windows: The buffer size is too small; WithBufferSize() can be used to increase it. + // - kqueue, fen: Not used. Errors chan error port windows.Handle // Handle to completion port @@ -35,40 +147,48 @@ type readDirChangesW struct { closed bool // Set to true when Close() is first called } -func newBackend(ev chan Event, errs chan error) (backend, error) { - return newBufferedBackend(50, ev, errs) +// NewWatcher creates a new Watcher. +func NewWatcher() (*Watcher, error) { + return NewBufferedWatcher(50) } -func newBufferedBackend(sz uint, ev chan Event, errs chan error) (backend, error) { +// NewBufferedWatcher creates a new Watcher with a buffered Watcher.Events +// channel. +// +// The main use case for this is situations with a very large number of events +// where the kernel buffer size can't be increased (e.g. due to lack of +// permissions). An unbuffered Watcher will perform better for almost all use +// cases, and whenever possible you will be better off increasing the kernel +// buffers instead of adding a large userspace buffer. +func NewBufferedWatcher(sz uint) (*Watcher, error) { port, err := windows.CreateIoCompletionPort(windows.InvalidHandle, 0, 0, 0) if err != nil { return nil, os.NewSyscallError("CreateIoCompletionPort", err) } - w := &readDirChangesW{ - Events: ev, - Errors: errs, + w := &Watcher{ port: port, watches: make(watchMap), input: make(chan *input, 1), + Events: make(chan Event, sz), + Errors: make(chan error), quit: make(chan chan<- error, 1), } go w.readEvents() return w, nil } -func (w *readDirChangesW) isClosed() bool { +func (w *Watcher) isClosed() bool { w.mu.Lock() defer w.mu.Unlock() return w.closed } -func (w *readDirChangesW) sendEvent(name, renamedFrom string, mask uint64) bool { +func (w *Watcher) sendEvent(name string, mask uint64) bool { if mask == 0 { return false } event := w.newEvent(name, uint32(mask)) - event.renamedFrom = renamedFrom select { case ch := <-w.quit: w.quit <- ch @@ -78,19 +198,17 @@ func (w *readDirChangesW) sendEvent(name, renamedFrom string, mask uint64) bool } // Returns true if the error was sent, or false if watcher is closed. -func (w *readDirChangesW) sendError(err error) bool { - if err == nil { - return true - } +func (w *Watcher) sendError(err error) bool { select { case w.Errors <- err: return true case <-w.quit: - return false } + return false } -func (w *readDirChangesW) Close() error { +// Close removes all watches and closes the Events channel. +func (w *Watcher) Close() error { if w.isClosed() { return nil } @@ -108,21 +226,57 @@ func (w *readDirChangesW) Close() error { return <-ch } -func (w *readDirChangesW) Add(name string) error { return w.AddWith(name) } +// Add starts monitoring the path for changes. +// +// A path can only be watched once; watching it more than once is a no-op and will +// not return an error. Paths that do not yet exist on the filesystem cannot be +// watched. +// +// A watch will be automatically removed if the watched path is deleted or +// renamed. The exception is the Windows backend, which doesn't remove the +// watcher on renames. +// +// Notifications on network filesystems (NFS, SMB, FUSE, etc.) or special +// filesystems (/proc, /sys, etc.) generally don't work. +// +// Returns [ErrClosed] if [Watcher.Close] was called. +// +// See [Watcher.AddWith] for a version that allows adding options. +// +// # Watching directories +// +// All files in a directory are monitored, including new files that are created +// after the watcher is started. Subdirectories are not watched (i.e. it's +// non-recursive). +// +// # Watching files +// +// Watching individual files (rather than directories) is generally not +// recommended as many programs (especially editors) update files atomically: it +// will write to a temporary file which is then moved to to destination, +// overwriting the original (or some variant thereof). The watcher on the +// original file is now lost, as that no longer exists. +// +// The upshot of this is that a power failure or crash won't leave a +// half-written file. +// +// Watch the parent directory and use Event.Name to filter out files you're not +// interested in. There is an example of this in cmd/fsnotify/file.go. +func (w *Watcher) Add(name string) error { return w.AddWith(name) } -func (w *readDirChangesW) AddWith(name string, opts ...addOpt) error { +// AddWith is like [Watcher.Add], but allows adding options. When using Add() +// the defaults described below are used. +// +// Possible options are: +// +// - [WithBufferSize] sets the buffer size for the Windows backend; no-op on +// other platforms. The default is 64K (65536 bytes). +func (w *Watcher) AddWith(name string, opts ...addOpt) error { if w.isClosed() { return ErrClosed } - if debug { - fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s AddWith(%q)\n", - time.Now().Format("15:04:05.000000000"), filepath.ToSlash(name)) - } with := getOptions(opts...) - if !w.xSupports(with.op) { - return fmt.Errorf("%w: %s", xErrUnsupported, with.op) - } if with.bufsize < 4096 { return fmt.Errorf("fsnotify.WithBufferSize: buffer size cannot be smaller than 4096 bytes") } @@ -141,14 +295,18 @@ func (w *readDirChangesW) AddWith(name string, opts ...addOpt) error { return <-in.reply } -func (w *readDirChangesW) Remove(name string) error { +// Remove stops monitoring the path for changes. +// +// Directories are always removed non-recursively. For example, if you added +// /tmp/dir and /tmp/dir/subdir then you will need to remove both. +// +// Removing a path that has not yet been added returns [ErrNonExistentWatch]. +// +// Returns nil if [Watcher.Close] was called. +func (w *Watcher) Remove(name string) error { if w.isClosed() { return nil } - if debug { - fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s Remove(%q)\n", - time.Now().Format("15:04:05.000000000"), filepath.ToSlash(name)) - } in := &input{ op: opRemoveWatch, @@ -162,7 +320,11 @@ func (w *readDirChangesW) Remove(name string) error { return <-in.reply } -func (w *readDirChangesW) WatchList() []string { +// WatchList returns all paths explicitly added with [Watcher.Add] (and are not +// yet removed). +// +// Returns nil if [Watcher.Close] was called. +func (w *Watcher) WatchList() []string { if w.isClosed() { return nil } @@ -173,13 +335,7 @@ func (w *readDirChangesW) WatchList() []string { entries := make([]string, 0, len(w.watches)) for _, entry := range w.watches { for _, watchEntry := range entry { - for name := range watchEntry.names { - entries = append(entries, filepath.Join(watchEntry.path, name)) - } - // the directory itself is being watched - if watchEntry.mask != 0 { - entries = append(entries, watchEntry.path) - } + entries = append(entries, watchEntry.path) } } @@ -205,7 +361,7 @@ const ( sysFSIGNORED = 0x8000 ) -func (w *readDirChangesW) newEvent(name string, mask uint32) Event { +func (w *Watcher) newEvent(name string, mask uint32) Event { e := Event{Name: name} if mask&sysFSCREATE == sysFSCREATE || mask&sysFSMOVEDTO == sysFSMOVEDTO { e.Op |= Create @@ -261,7 +417,7 @@ type ( watchMap map[uint32]indexMap ) -func (w *readDirChangesW) wakeupReader() error { +func (w *Watcher) wakeupReader() error { err := windows.PostQueuedCompletionStatus(w.port, 0, 0, nil) if err != nil { return os.NewSyscallError("PostQueuedCompletionStatus", err) @@ -269,7 +425,7 @@ func (w *readDirChangesW) wakeupReader() error { return nil } -func (w *readDirChangesW) getDir(pathname string) (dir string, err error) { +func (w *Watcher) getDir(pathname string) (dir string, err error) { attr, err := windows.GetFileAttributes(windows.StringToUTF16Ptr(pathname)) if err != nil { return "", os.NewSyscallError("GetFileAttributes", err) @@ -283,7 +439,7 @@ func (w *readDirChangesW) getDir(pathname string) (dir string, err error) { return } -func (w *readDirChangesW) getIno(path string) (ino *inode, err error) { +func (w *Watcher) getIno(path string) (ino *inode, err error) { h, err := windows.CreateFile(windows.StringToUTF16Ptr(path), windows.FILE_LIST_DIRECTORY, windows.FILE_SHARE_READ|windows.FILE_SHARE_WRITE|windows.FILE_SHARE_DELETE, @@ -326,8 +482,9 @@ func (m watchMap) set(ino *inode, watch *watch) { } // Must run within the I/O thread. -func (w *readDirChangesW) addWatch(pathname string, flags uint64, bufsize int) error { - pathname, recurse := recursivePath(pathname) +func (w *Watcher) addWatch(pathname string, flags uint64, bufsize int) error { + //pathname, recurse := recursivePath(pathname) + recurse := false dir, err := w.getDir(pathname) if err != nil { @@ -381,7 +538,7 @@ func (w *readDirChangesW) addWatch(pathname string, flags uint64, bufsize int) e } // Must run within the I/O thread. -func (w *readDirChangesW) remWatch(pathname string) error { +func (w *Watcher) remWatch(pathname string) error { pathname, recurse := recursivePath(pathname) dir, err := w.getDir(pathname) @@ -409,11 +566,11 @@ func (w *readDirChangesW) remWatch(pathname string) error { return fmt.Errorf("%w: %s", ErrNonExistentWatch, pathname) } if pathname == dir { - w.sendEvent(watch.path, "", watch.mask&sysFSIGNORED) + w.sendEvent(watch.path, watch.mask&sysFSIGNORED) watch.mask = 0 } else { name := filepath.Base(pathname) - w.sendEvent(filepath.Join(watch.path, name), "", watch.names[name]&sysFSIGNORED) + w.sendEvent(filepath.Join(watch.path, name), watch.names[name]&sysFSIGNORED) delete(watch.names, name) } @@ -421,23 +578,23 @@ func (w *readDirChangesW) remWatch(pathname string) error { } // Must run within the I/O thread. -func (w *readDirChangesW) deleteWatch(watch *watch) { +func (w *Watcher) deleteWatch(watch *watch) { for name, mask := range watch.names { if mask&provisional == 0 { - w.sendEvent(filepath.Join(watch.path, name), "", mask&sysFSIGNORED) + w.sendEvent(filepath.Join(watch.path, name), mask&sysFSIGNORED) } delete(watch.names, name) } if watch.mask != 0 { if watch.mask&provisional == 0 { - w.sendEvent(watch.path, "", watch.mask&sysFSIGNORED) + w.sendEvent(watch.path, watch.mask&sysFSIGNORED) } watch.mask = 0 } } // Must run within the I/O thread. -func (w *readDirChangesW) startRead(watch *watch) error { +func (w *Watcher) startRead(watch *watch) error { err := windows.CancelIo(watch.ino.handle) if err != nil { w.sendError(os.NewSyscallError("CancelIo", err)) @@ -467,7 +624,7 @@ func (w *readDirChangesW) startRead(watch *watch) error { err := os.NewSyscallError("ReadDirectoryChanges", rdErr) if rdErr == windows.ERROR_ACCESS_DENIED && watch.mask&provisional == 0 { // Watched directory was probably removed - w.sendEvent(watch.path, "", watch.mask&sysFSDELETESELF) + w.sendEvent(watch.path, watch.mask&sysFSDELETESELF) err = nil } w.deleteWatch(watch) @@ -480,7 +637,7 @@ func (w *readDirChangesW) startRead(watch *watch) error { // readEvents reads from the I/O completion port, converts the // received events into Event objects and sends them via the Events channel. // Entry point to the I/O thread. -func (w *readDirChangesW) readEvents() { +func (w *Watcher) readEvents() { var ( n uint32 key uintptr @@ -543,7 +700,7 @@ func (w *readDirChangesW) readEvents() { } case windows.ERROR_ACCESS_DENIED: // Watched directory was probably removed - w.sendEvent(watch.path, "", watch.mask&sysFSDELETESELF) + w.sendEvent(watch.path, watch.mask&sysFSDELETESELF) w.deleteWatch(watch) w.startRead(watch) continue @@ -576,10 +733,6 @@ func (w *readDirChangesW) readEvents() { name := windows.UTF16ToString(buf) fullname := filepath.Join(watch.path, name) - if debug { - internal.Debug(fullname, raw.Action) - } - var mask uint64 switch raw.Action { case windows.FILE_ACTION_REMOVED: @@ -608,22 +761,21 @@ func (w *readDirChangesW) readEvents() { } } + sendNameEvent := func() { + w.sendEvent(fullname, watch.names[name]&mask) + } if raw.Action != windows.FILE_ACTION_RENAMED_NEW_NAME { - w.sendEvent(fullname, "", watch.names[name]&mask) + sendNameEvent() } if raw.Action == windows.FILE_ACTION_REMOVED { - w.sendEvent(fullname, "", watch.names[name]&sysFSIGNORED) + w.sendEvent(fullname, watch.names[name]&sysFSIGNORED) delete(watch.names, name) } - if watch.rename != "" && raw.Action == windows.FILE_ACTION_RENAMED_NEW_NAME { - w.sendEvent(fullname, filepath.Join(watch.path, watch.rename), watch.mask&w.toFSnotifyFlags(raw.Action)) - } else { - w.sendEvent(fullname, "", watch.mask&w.toFSnotifyFlags(raw.Action)) - } - + w.sendEvent(fullname, watch.mask&w.toFSnotifyFlags(raw.Action)) if raw.Action == windows.FILE_ACTION_RENAMED_NEW_NAME { - w.sendEvent(filepath.Join(watch.path, watch.rename), "", watch.names[name]&mask) + fullname = filepath.Join(watch.path, watch.rename) + sendNameEvent() } // Move to the next event in the buffer @@ -635,7 +787,8 @@ func (w *readDirChangesW) readEvents() { // Error! if offset >= n { //lint:ignore ST1005 Windows should be capitalized - w.sendError(errors.New("Windows system assumed buffer larger than it is, events have likely been missed")) + w.sendError(errors.New( + "Windows system assumed buffer larger than it is, events have likely been missed")) break } } @@ -646,7 +799,7 @@ func (w *readDirChangesW) readEvents() { } } -func (w *readDirChangesW) toWindowsFlags(mask uint64) uint32 { +func (w *Watcher) toWindowsFlags(mask uint64) uint32 { var m uint32 if mask&sysFSMODIFY != 0 { m |= windows.FILE_NOTIFY_CHANGE_LAST_WRITE @@ -657,7 +810,7 @@ func (w *readDirChangesW) toWindowsFlags(mask uint64) uint32 { return m } -func (w *readDirChangesW) toFSnotifyFlags(action uint32) uint64 { +func (w *Watcher) toFSnotifyFlags(action uint32) uint64 { switch action { case windows.FILE_ACTION_ADDED: return sysFSCREATE @@ -672,11 +825,3 @@ func (w *readDirChangesW) toFSnotifyFlags(action uint32) uint64 { } return 0 } - -func (w *readDirChangesW) xSupports(op Op) bool { - if op.Has(xUnportableOpen) || op.Has(xUnportableRead) || - op.Has(xUnportableCloseWrite) || op.Has(xUnportableCloseRead) { - return false - } - return true -} diff --git a/vendor/github.com/fsnotify/fsnotify/fsnotify.go b/vendor/github.com/fsnotify/fsnotify/fsnotify.go index 0760efe91..24c99cc49 100644 --- a/vendor/github.com/fsnotify/fsnotify/fsnotify.go +++ b/vendor/github.com/fsnotify/fsnotify/fsnotify.go @@ -3,146 +3,19 @@ // // Currently supported systems: // -// - Linux via inotify -// - BSD, macOS via kqueue -// - Windows via ReadDirectoryChangesW -// - illumos via FEN -// -// # FSNOTIFY_DEBUG -// -// Set the FSNOTIFY_DEBUG environment variable to "1" to print debug messages to -// stderr. This can be useful to track down some problems, especially in cases -// where fsnotify is used as an indirect dependency. -// -// Every event will be printed as soon as there's something useful to print, -// with as little processing from fsnotify. -// -// Example output: -// -// FSNOTIFY_DEBUG: 11:34:23.633087586 256:IN_CREATE → "/tmp/file-1" -// FSNOTIFY_DEBUG: 11:34:23.633202319 4:IN_ATTRIB → "/tmp/file-1" -// FSNOTIFY_DEBUG: 11:34:28.989728764 512:IN_DELETE → "/tmp/file-1" +// Linux 2.6.32+ via inotify +// BSD, macOS via kqueue +// Windows via ReadDirectoryChangesW +// illumos via FEN package fsnotify import ( "errors" "fmt" - "os" "path/filepath" "strings" ) -// 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. -// -// # Windows notes -// -// Paths can be added as "C:\\path\\to\\dir", but forward slashes -// ("C:/path/to/dir") will also work. -// -// When a watched directory is removed it will always send an event for the -// directory itself, but may not send events for all files in that directory. -// Sometimes it will send events for all files, sometimes it will send no -// events, and often only for some files. -// -// The default ReadDirectoryChangesW() buffer size is 64K, which is the largest -// value that is guaranteed to work with SMB filesystems. If you have many -// events in quick succession this may not be enough, and you will have to use -// [WithBufferSize] to increase the value. -type Watcher struct { - b backend - - // 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, and you may - // want to wait until you've stopped receiving them - // (see the dedup example in cmd/fsnotify). - // - // Some systems may send Write event for directories - // when the directory content changes. - // - // 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 - // when a file is truncated. On Windows it's never - // sent. - Events chan Event - - // Errors sends any errors. - Errors chan error -} - // Event represents a file system notification. type Event struct { // Path to the file or directory. @@ -157,16 +30,6 @@ type Event struct { // This is a bitmask and some systems may send multiple operations at once. // Use the Event.Has() method instead of comparing with ==. Op Op - - // Create events will have this set to the old path if it's a rename. This - // only works when both the source and destination are watched. It's not - // reliable when watching individual files, only directories. - // - // For example "mv /tmp/file /tmp/rename" will emit: - // - // Event{Op: Rename, Name: "/tmp/file"} - // Event{Op: Create, Name: "/tmp/rename", RenamedFrom: "/tmp/file"} - renamedFrom string } // Op describes a set of file operations. @@ -187,7 +50,7 @@ const ( // example "remove to trash" is often a rename). Remove - // The path was renamed to something else; any watches on it will be + // The path was renamed to something else; any watched on it will be // removed. Rename @@ -197,155 +60,15 @@ const ( // get triggered very frequently by some software. For example, Spotlight // indexing on macOS, anti-virus software, backup software, etc. Chmod - - // File descriptor was opened. - // - // Only works on Linux and FreeBSD. - xUnportableOpen - - // File was read from. - // - // Only works on Linux and FreeBSD. - xUnportableRead - - // File opened for writing was closed. - // - // Only works on Linux and FreeBSD. - // - // The advantage of using this over Write is that it's more reliable than - // waiting for Write events to stop. It's also faster (if you're not - // listening to Write events): copying a file of a few GB can easily - // generate tens of thousands of Write events in a short span of time. - xUnportableCloseWrite - - // File opened for reading was closed. - // - // Only works on Linux and FreeBSD. - xUnportableCloseRead ) +// Common errors that can be reported. var ( - // ErrNonExistentWatch is used when Remove() is called on a path that's not - // added. ErrNonExistentWatch = errors.New("fsnotify: can't remove non-existent watch") - - // ErrClosed is used when trying to operate on a closed Watcher. - ErrClosed = errors.New("fsnotify: watcher already closed") - - // ErrEventOverflow is reported from the Errors channel when there are too - // many events: - // - // - inotify: inotify returns IN_Q_OVERFLOW – because there are too - // many queued events (the fs.inotify.max_queued_events - // sysctl can be used to increase this). - // - windows: The buffer size is too small; WithBufferSize() can be used to increase it. - // - kqueue, fen: Not used. - ErrEventOverflow = errors.New("fsnotify: queue or buffer overflow") - - // ErrUnsupported is returned by AddWith() when WithOps() specified an - // Unportable event that's not supported on this platform. - xErrUnsupported = errors.New("fsnotify: not supported with this backend") + ErrEventOverflow = errors.New("fsnotify: queue or buffer overflow") + ErrClosed = errors.New("fsnotify: watcher already closed") ) -// NewWatcher creates a new Watcher. -func NewWatcher() (*Watcher, error) { - ev, errs := make(chan Event), make(chan error) - b, err := newBackend(ev, errs) - if err != nil { - return nil, err - } - return &Watcher{b: b, Events: ev, Errors: errs}, nil -} - -// NewBufferedWatcher creates a new Watcher with a buffered Watcher.Events -// channel. -// -// The main use case for this is situations with a very large number of events -// where the kernel buffer size can't be increased (e.g. due to lack of -// permissions). An unbuffered Watcher will perform better for almost all use -// cases, and whenever possible you will be better off increasing the kernel -// buffers instead of adding a large userspace buffer. -func NewBufferedWatcher(sz uint) (*Watcher, error) { - ev, errs := make(chan Event), make(chan error) - b, err := newBufferedBackend(sz, ev, errs) - if err != nil { - return nil, err - } - return &Watcher{b: b, Events: ev, Errors: errs}, nil -} - -// Add starts monitoring the path for changes. -// -// A path can only be watched once; watching it more than once is a no-op and will -// not return an error. Paths that do not yet exist on the filesystem cannot be -// watched. -// -// A watch will be automatically removed if the watched path is deleted or -// renamed. The exception is the Windows backend, which doesn't remove the -// watcher on renames. -// -// Notifications on network filesystems (NFS, SMB, FUSE, etc.) or special -// filesystems (/proc, /sys, etc.) generally don't work. -// -// Returns [ErrClosed] if [Watcher.Close] was called. -// -// See [Watcher.AddWith] for a version that allows adding options. -// -// # Watching directories -// -// All files in a directory are monitored, including new files that are created -// after the watcher is started. Subdirectories are not watched (i.e. it's -// non-recursive). -// -// # Watching files -// -// Watching individual files (rather than directories) is generally not -// recommended as many programs (especially editors) update files atomically: it -// will write to a temporary file which is then moved to destination, -// overwriting the original (or some variant thereof). The watcher on the -// original file is now lost, as that no longer exists. -// -// The upshot of this is that a power failure or crash won't leave a -// half-written file. -// -// Watch the parent directory and use Event.Name to filter out files you're not -// interested in. There is an example of this in cmd/fsnotify/file.go. -func (w *Watcher) Add(path string) error { return w.b.Add(path) } - -// AddWith is like [Watcher.Add], but allows adding options. When using Add() -// the defaults described below are used. -// -// Possible options are: -// -// - [WithBufferSize] sets the buffer size for the Windows backend; no-op on -// other platforms. The default is 64K (65536 bytes). -func (w *Watcher) AddWith(path string, opts ...addOpt) error { return w.b.AddWith(path, opts...) } - -// Remove stops monitoring the path for changes. -// -// Directories are always removed non-recursively. For example, if you added -// /tmp/dir and /tmp/dir/subdir then you will need to remove both. -// -// Removing a path that has not yet been added returns [ErrNonExistentWatch]. -// -// Returns nil if [Watcher.Close] was called. -func (w *Watcher) Remove(path string) error { return w.b.Remove(path) } - -// Close removes all watches and closes the Events channel. -func (w *Watcher) Close() error { return w.b.Close() } - -// WatchList returns all paths explicitly added with [Watcher.Add] (and are not -// yet removed). -// -// Returns nil if [Watcher.Close] was called. -func (w *Watcher) WatchList() []string { return w.b.WatchList() } - -// Supports reports if all the listed operations are supported by this platform. -// -// Create, Write, Remove, Rename, and Chmod are always supported. It can only -// return false for an Op starting with Unportable. -func (w *Watcher) xSupports(op Op) bool { return w.b.xSupports(op) } - func (o Op) String() string { var b strings.Builder if o.Has(Create) { @@ -357,18 +80,6 @@ func (o Op) String() string { if o.Has(Write) { b.WriteString("|WRITE") } - if o.Has(xUnportableOpen) { - b.WriteString("|OPEN") - } - if o.Has(xUnportableRead) { - b.WriteString("|READ") - } - if o.Has(xUnportableCloseWrite) { - b.WriteString("|CLOSE_WRITE") - } - if o.Has(xUnportableCloseRead) { - b.WriteString("|CLOSE_READ") - } if o.Has(Rename) { b.WriteString("|RENAME") } @@ -389,48 +100,24 @@ func (e Event) Has(op Op) bool { return e.Op.Has(op) } // String returns a string representation of the event with their path. func (e Event) String() string { - if e.renamedFrom != "" { - return fmt.Sprintf("%-13s %q ← %q", e.Op.String(), e.Name, e.renamedFrom) - } return fmt.Sprintf("%-13s %q", e.Op.String(), e.Name) } type ( - backend interface { - Add(string) error - AddWith(string, ...addOpt) error - Remove(string) error - WatchList() []string - Close() error - xSupports(Op) bool - } addOpt func(opt *withOpts) withOpts struct { - bufsize int - op Op - noFollow bool - sendCreate bool + bufsize int } ) -var debug = func() bool { - // Check for exactly "1" (rather than mere existence) so we can add - // options/flags in the future. I don't know if we ever want that, but it's - // nice to leave the option open. - return os.Getenv("FSNOTIFY_DEBUG") == "1" -}() - var defaultOpts = withOpts{ bufsize: 65536, // 64K - op: Create | Write | Remove | Rename | Chmod, } func getOptions(opts ...addOpt) withOpts { with := defaultOpts for _, o := range opts { - if o != nil { - o(&with) - } + o(&with) } return with } @@ -449,44 +136,9 @@ func WithBufferSize(bytes int) addOpt { return func(opt *withOpts) { opt.bufsize = bytes } } -// WithOps sets which operations to listen for. The default is [Create], -// [Write], [Remove], [Rename], and [Chmod]. -// -// Excluding operations you're not interested in can save quite a bit of CPU -// time; in some use cases there may be hundreds of thousands of useless Write -// or Chmod operations per second. -// -// This can also be used to add unportable operations not supported by all -// platforms; unportable operations all start with "Unportable": -// [UnportableOpen], [UnportableRead], [UnportableCloseWrite], and -// [UnportableCloseRead]. -// -// AddWith returns an error when using an unportable operation that's not -// supported. Use [Watcher.Support] to check for support. -func withOps(op Op) addOpt { - return func(opt *withOpts) { opt.op = op } -} - -// WithNoFollow disables following symlinks, so the symlinks themselves are -// watched. -func withNoFollow() addOpt { - return func(opt *withOpts) { opt.noFollow = true } -} - -// "Internal" option for recursive watches on inotify. -func withCreate() addOpt { - return func(opt *withOpts) { opt.sendCreate = true } -} - -var enableRecurse = false - // Check if this path is recursive (ends with "/..." or "\..."), and return the // path with the /... stripped. func recursivePath(path string) (string, bool) { - path = filepath.Clean(path) - if !enableRecurse { // Only enabled in tests for now. - return path, false - } if filepath.Base(path) == "..." { return filepath.Dir(path), true } diff --git a/vendor/github.com/fsnotify/fsnotify/internal/darwin.go b/vendor/github.com/fsnotify/fsnotify/internal/darwin.go deleted file mode 100644 index b0eab1009..000000000 --- a/vendor/github.com/fsnotify/fsnotify/internal/darwin.go +++ /dev/null @@ -1,39 +0,0 @@ -//go:build darwin - -package internal - -import ( - "syscall" - - "golang.org/x/sys/unix" -) - -var ( - SyscallEACCES = syscall.EACCES - UnixEACCES = unix.EACCES -) - -var maxfiles uint64 - -// Go 1.19 will do this automatically: https://go-review.googlesource.com/c/go/+/393354/ -func SetRlimit() { - var l syscall.Rlimit - err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &l) - if err == nil && l.Cur != l.Max { - l.Cur = l.Max - syscall.Setrlimit(syscall.RLIMIT_NOFILE, &l) - } - maxfiles = l.Cur - - if n, err := syscall.SysctlUint32("kern.maxfiles"); err == nil && uint64(n) < maxfiles { - maxfiles = uint64(n) - } - - if n, err := syscall.SysctlUint32("kern.maxfilesperproc"); err == nil && uint64(n) < maxfiles { - maxfiles = uint64(n) - } -} - -func Maxfiles() uint64 { return maxfiles } -func Mkfifo(path string, mode uint32) error { return unix.Mkfifo(path, mode) } -func Mknod(path string, mode uint32, dev int) error { return unix.Mknod(path, mode, dev) } diff --git a/vendor/github.com/fsnotify/fsnotify/internal/debug_darwin.go b/vendor/github.com/fsnotify/fsnotify/internal/debug_darwin.go deleted file mode 100644 index 928319fb0..000000000 --- a/vendor/github.com/fsnotify/fsnotify/internal/debug_darwin.go +++ /dev/null @@ -1,57 +0,0 @@ -package internal - -import "golang.org/x/sys/unix" - -var names = []struct { - n string - m uint32 -}{ - {"NOTE_ABSOLUTE", unix.NOTE_ABSOLUTE}, - {"NOTE_ATTRIB", unix.NOTE_ATTRIB}, - {"NOTE_BACKGROUND", unix.NOTE_BACKGROUND}, - {"NOTE_CHILD", unix.NOTE_CHILD}, - {"NOTE_CRITICAL", unix.NOTE_CRITICAL}, - {"NOTE_DELETE", unix.NOTE_DELETE}, - {"NOTE_EXEC", unix.NOTE_EXEC}, - {"NOTE_EXIT", unix.NOTE_EXIT}, - {"NOTE_EXITSTATUS", unix.NOTE_EXITSTATUS}, - {"NOTE_EXIT_CSERROR", unix.NOTE_EXIT_CSERROR}, - {"NOTE_EXIT_DECRYPTFAIL", unix.NOTE_EXIT_DECRYPTFAIL}, - {"NOTE_EXIT_DETAIL", unix.NOTE_EXIT_DETAIL}, - {"NOTE_EXIT_DETAIL_MASK", unix.NOTE_EXIT_DETAIL_MASK}, - {"NOTE_EXIT_MEMORY", unix.NOTE_EXIT_MEMORY}, - {"NOTE_EXIT_REPARENTED", unix.NOTE_EXIT_REPARENTED}, - {"NOTE_EXTEND", unix.NOTE_EXTEND}, - {"NOTE_FFAND", unix.NOTE_FFAND}, - {"NOTE_FFCOPY", unix.NOTE_FFCOPY}, - {"NOTE_FFCTRLMASK", unix.NOTE_FFCTRLMASK}, - {"NOTE_FFLAGSMASK", unix.NOTE_FFLAGSMASK}, - {"NOTE_FFNOP", unix.NOTE_FFNOP}, - {"NOTE_FFOR", unix.NOTE_FFOR}, - {"NOTE_FORK", unix.NOTE_FORK}, - {"NOTE_FUNLOCK", unix.NOTE_FUNLOCK}, - {"NOTE_LEEWAY", unix.NOTE_LEEWAY}, - {"NOTE_LINK", unix.NOTE_LINK}, - {"NOTE_LOWAT", unix.NOTE_LOWAT}, - {"NOTE_MACHTIME", unix.NOTE_MACHTIME}, - {"NOTE_MACH_CONTINUOUS_TIME", unix.NOTE_MACH_CONTINUOUS_TIME}, - {"NOTE_NONE", unix.NOTE_NONE}, - {"NOTE_NSECONDS", unix.NOTE_NSECONDS}, - {"NOTE_OOB", unix.NOTE_OOB}, - //{"NOTE_PCTRLMASK", unix.NOTE_PCTRLMASK}, -0x100000 (?!) - {"NOTE_PDATAMASK", unix.NOTE_PDATAMASK}, - {"NOTE_REAP", unix.NOTE_REAP}, - {"NOTE_RENAME", unix.NOTE_RENAME}, - {"NOTE_REVOKE", unix.NOTE_REVOKE}, - {"NOTE_SECONDS", unix.NOTE_SECONDS}, - {"NOTE_SIGNAL", unix.NOTE_SIGNAL}, - {"NOTE_TRACK", unix.NOTE_TRACK}, - {"NOTE_TRACKERR", unix.NOTE_TRACKERR}, - {"NOTE_TRIGGER", unix.NOTE_TRIGGER}, - {"NOTE_USECONDS", unix.NOTE_USECONDS}, - {"NOTE_VM_ERROR", unix.NOTE_VM_ERROR}, - {"NOTE_VM_PRESSURE", unix.NOTE_VM_PRESSURE}, - {"NOTE_VM_PRESSURE_SUDDEN_TERMINATE", unix.NOTE_VM_PRESSURE_SUDDEN_TERMINATE}, - {"NOTE_VM_PRESSURE_TERMINATE", unix.NOTE_VM_PRESSURE_TERMINATE}, - {"NOTE_WRITE", unix.NOTE_WRITE}, -} diff --git a/vendor/github.com/fsnotify/fsnotify/internal/debug_dragonfly.go b/vendor/github.com/fsnotify/fsnotify/internal/debug_dragonfly.go deleted file mode 100644 index 3186b0c34..000000000 --- a/vendor/github.com/fsnotify/fsnotify/internal/debug_dragonfly.go +++ /dev/null @@ -1,33 +0,0 @@ -package internal - -import "golang.org/x/sys/unix" - -var names = []struct { - n string - m uint32 -}{ - {"NOTE_ATTRIB", unix.NOTE_ATTRIB}, - {"NOTE_CHILD", unix.NOTE_CHILD}, - {"NOTE_DELETE", unix.NOTE_DELETE}, - {"NOTE_EXEC", unix.NOTE_EXEC}, - {"NOTE_EXIT", unix.NOTE_EXIT}, - {"NOTE_EXTEND", unix.NOTE_EXTEND}, - {"NOTE_FFAND", unix.NOTE_FFAND}, - {"NOTE_FFCOPY", unix.NOTE_FFCOPY}, - {"NOTE_FFCTRLMASK", unix.NOTE_FFCTRLMASK}, - {"NOTE_FFLAGSMASK", unix.NOTE_FFLAGSMASK}, - {"NOTE_FFNOP", unix.NOTE_FFNOP}, - {"NOTE_FFOR", unix.NOTE_FFOR}, - {"NOTE_FORK", unix.NOTE_FORK}, - {"NOTE_LINK", unix.NOTE_LINK}, - {"NOTE_LOWAT", unix.NOTE_LOWAT}, - {"NOTE_OOB", unix.NOTE_OOB}, - {"NOTE_PCTRLMASK", unix.NOTE_PCTRLMASK}, - {"NOTE_PDATAMASK", unix.NOTE_PDATAMASK}, - {"NOTE_RENAME", unix.NOTE_RENAME}, - {"NOTE_REVOKE", unix.NOTE_REVOKE}, - {"NOTE_TRACK", unix.NOTE_TRACK}, - {"NOTE_TRACKERR", unix.NOTE_TRACKERR}, - {"NOTE_TRIGGER", unix.NOTE_TRIGGER}, - {"NOTE_WRITE", unix.NOTE_WRITE}, -} diff --git a/vendor/github.com/fsnotify/fsnotify/internal/debug_freebsd.go b/vendor/github.com/fsnotify/fsnotify/internal/debug_freebsd.go deleted file mode 100644 index f69fdb930..000000000 --- a/vendor/github.com/fsnotify/fsnotify/internal/debug_freebsd.go +++ /dev/null @@ -1,42 +0,0 @@ -package internal - -import "golang.org/x/sys/unix" - -var names = []struct { - n string - m uint32 -}{ - {"NOTE_ABSTIME", unix.NOTE_ABSTIME}, - {"NOTE_ATTRIB", unix.NOTE_ATTRIB}, - {"NOTE_CHILD", unix.NOTE_CHILD}, - {"NOTE_CLOSE", unix.NOTE_CLOSE}, - {"NOTE_CLOSE_WRITE", unix.NOTE_CLOSE_WRITE}, - {"NOTE_DELETE", unix.NOTE_DELETE}, - {"NOTE_EXEC", unix.NOTE_EXEC}, - {"NOTE_EXIT", unix.NOTE_EXIT}, - {"NOTE_EXTEND", unix.NOTE_EXTEND}, - {"NOTE_FFAND", unix.NOTE_FFAND}, - {"NOTE_FFCOPY", unix.NOTE_FFCOPY}, - {"NOTE_FFCTRLMASK", unix.NOTE_FFCTRLMASK}, - {"NOTE_FFLAGSMASK", unix.NOTE_FFLAGSMASK}, - {"NOTE_FFNOP", unix.NOTE_FFNOP}, - {"NOTE_FFOR", unix.NOTE_FFOR}, - {"NOTE_FILE_POLL", unix.NOTE_FILE_POLL}, - {"NOTE_FORK", unix.NOTE_FORK}, - {"NOTE_LINK", unix.NOTE_LINK}, - {"NOTE_LOWAT", unix.NOTE_LOWAT}, - {"NOTE_MSECONDS", unix.NOTE_MSECONDS}, - {"NOTE_NSECONDS", unix.NOTE_NSECONDS}, - {"NOTE_OPEN", unix.NOTE_OPEN}, - {"NOTE_PCTRLMASK", unix.NOTE_PCTRLMASK}, - {"NOTE_PDATAMASK", unix.NOTE_PDATAMASK}, - {"NOTE_READ", unix.NOTE_READ}, - {"NOTE_RENAME", unix.NOTE_RENAME}, - {"NOTE_REVOKE", unix.NOTE_REVOKE}, - {"NOTE_SECONDS", unix.NOTE_SECONDS}, - {"NOTE_TRACK", unix.NOTE_TRACK}, - {"NOTE_TRACKERR", unix.NOTE_TRACKERR}, - {"NOTE_TRIGGER", unix.NOTE_TRIGGER}, - {"NOTE_USECONDS", unix.NOTE_USECONDS}, - {"NOTE_WRITE", unix.NOTE_WRITE}, -} diff --git a/vendor/github.com/fsnotify/fsnotify/internal/debug_kqueue.go b/vendor/github.com/fsnotify/fsnotify/internal/debug_kqueue.go deleted file mode 100644 index 607e683bd..000000000 --- a/vendor/github.com/fsnotify/fsnotify/internal/debug_kqueue.go +++ /dev/null @@ -1,32 +0,0 @@ -//go:build freebsd || openbsd || netbsd || dragonfly || darwin - -package internal - -import ( - "fmt" - "os" - "strings" - "time" - - "golang.org/x/sys/unix" -) - -func Debug(name string, kevent *unix.Kevent_t) { - mask := uint32(kevent.Fflags) - - var ( - l []string - unknown = mask - ) - for _, n := range names { - if mask&n.m == n.m { - l = append(l, n.n) - unknown ^= n.m - } - } - if unknown > 0 { - l = append(l, fmt.Sprintf("0x%x", unknown)) - } - fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s %10d:%-60s → %q\n", - time.Now().Format("15:04:05.000000000"), mask, strings.Join(l, " | "), name) -} diff --git a/vendor/github.com/fsnotify/fsnotify/internal/debug_linux.go b/vendor/github.com/fsnotify/fsnotify/internal/debug_linux.go deleted file mode 100644 index 35c734be4..000000000 --- a/vendor/github.com/fsnotify/fsnotify/internal/debug_linux.go +++ /dev/null @@ -1,56 +0,0 @@ -package internal - -import ( - "fmt" - "os" - "strings" - "time" - - "golang.org/x/sys/unix" -) - -func Debug(name string, mask, cookie uint32) { - names := []struct { - n string - m uint32 - }{ - {"IN_ACCESS", unix.IN_ACCESS}, - {"IN_ATTRIB", unix.IN_ATTRIB}, - {"IN_CLOSE", unix.IN_CLOSE}, - {"IN_CLOSE_NOWRITE", unix.IN_CLOSE_NOWRITE}, - {"IN_CLOSE_WRITE", unix.IN_CLOSE_WRITE}, - {"IN_CREATE", unix.IN_CREATE}, - {"IN_DELETE", unix.IN_DELETE}, - {"IN_DELETE_SELF", unix.IN_DELETE_SELF}, - {"IN_IGNORED", unix.IN_IGNORED}, - {"IN_ISDIR", unix.IN_ISDIR}, - {"IN_MODIFY", unix.IN_MODIFY}, - {"IN_MOVE", unix.IN_MOVE}, - {"IN_MOVED_FROM", unix.IN_MOVED_FROM}, - {"IN_MOVED_TO", unix.IN_MOVED_TO}, - {"IN_MOVE_SELF", unix.IN_MOVE_SELF}, - {"IN_OPEN", unix.IN_OPEN}, - {"IN_Q_OVERFLOW", unix.IN_Q_OVERFLOW}, - {"IN_UNMOUNT", unix.IN_UNMOUNT}, - } - - var ( - l []string - unknown = mask - ) - for _, n := range names { - if mask&n.m == n.m { - l = append(l, n.n) - unknown ^= n.m - } - } - if unknown > 0 { - l = append(l, fmt.Sprintf("0x%x", unknown)) - } - var c string - if cookie > 0 { - c = fmt.Sprintf("(cookie: %d) ", cookie) - } - fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s %-30s → %s%q\n", - time.Now().Format("15:04:05.000000000"), strings.Join(l, "|"), c, name) -} diff --git a/vendor/github.com/fsnotify/fsnotify/internal/debug_netbsd.go b/vendor/github.com/fsnotify/fsnotify/internal/debug_netbsd.go deleted file mode 100644 index e5b3b6f69..000000000 --- a/vendor/github.com/fsnotify/fsnotify/internal/debug_netbsd.go +++ /dev/null @@ -1,25 +0,0 @@ -package internal - -import "golang.org/x/sys/unix" - -var names = []struct { - n string - m uint32 -}{ - {"NOTE_ATTRIB", unix.NOTE_ATTRIB}, - {"NOTE_CHILD", unix.NOTE_CHILD}, - {"NOTE_DELETE", unix.NOTE_DELETE}, - {"NOTE_EXEC", unix.NOTE_EXEC}, - {"NOTE_EXIT", unix.NOTE_EXIT}, - {"NOTE_EXTEND", unix.NOTE_EXTEND}, - {"NOTE_FORK", unix.NOTE_FORK}, - {"NOTE_LINK", unix.NOTE_LINK}, - {"NOTE_LOWAT", unix.NOTE_LOWAT}, - {"NOTE_PCTRLMASK", unix.NOTE_PCTRLMASK}, - {"NOTE_PDATAMASK", unix.NOTE_PDATAMASK}, - {"NOTE_RENAME", unix.NOTE_RENAME}, - {"NOTE_REVOKE", unix.NOTE_REVOKE}, - {"NOTE_TRACK", unix.NOTE_TRACK}, - {"NOTE_TRACKERR", unix.NOTE_TRACKERR}, - {"NOTE_WRITE", unix.NOTE_WRITE}, -} diff --git a/vendor/github.com/fsnotify/fsnotify/internal/debug_openbsd.go b/vendor/github.com/fsnotify/fsnotify/internal/debug_openbsd.go deleted file mode 100644 index 1dd455bc5..000000000 --- a/vendor/github.com/fsnotify/fsnotify/internal/debug_openbsd.go +++ /dev/null @@ -1,28 +0,0 @@ -package internal - -import "golang.org/x/sys/unix" - -var names = []struct { - n string - m uint32 -}{ - {"NOTE_ATTRIB", unix.NOTE_ATTRIB}, - // {"NOTE_CHANGE", unix.NOTE_CHANGE}, // Not on 386? - {"NOTE_CHILD", unix.NOTE_CHILD}, - {"NOTE_DELETE", unix.NOTE_DELETE}, - {"NOTE_EOF", unix.NOTE_EOF}, - {"NOTE_EXEC", unix.NOTE_EXEC}, - {"NOTE_EXIT", unix.NOTE_EXIT}, - {"NOTE_EXTEND", unix.NOTE_EXTEND}, - {"NOTE_FORK", unix.NOTE_FORK}, - {"NOTE_LINK", unix.NOTE_LINK}, - {"NOTE_LOWAT", unix.NOTE_LOWAT}, - {"NOTE_PCTRLMASK", unix.NOTE_PCTRLMASK}, - {"NOTE_PDATAMASK", unix.NOTE_PDATAMASK}, - {"NOTE_RENAME", unix.NOTE_RENAME}, - {"NOTE_REVOKE", unix.NOTE_REVOKE}, - {"NOTE_TRACK", unix.NOTE_TRACK}, - {"NOTE_TRACKERR", unix.NOTE_TRACKERR}, - {"NOTE_TRUNCATE", unix.NOTE_TRUNCATE}, - {"NOTE_WRITE", unix.NOTE_WRITE}, -} diff --git a/vendor/github.com/fsnotify/fsnotify/internal/debug_solaris.go b/vendor/github.com/fsnotify/fsnotify/internal/debug_solaris.go deleted file mode 100644 index f1b2e73bd..000000000 --- a/vendor/github.com/fsnotify/fsnotify/internal/debug_solaris.go +++ /dev/null @@ -1,45 +0,0 @@ -package internal - -import ( - "fmt" - "os" - "strings" - "time" - - "golang.org/x/sys/unix" -) - -func Debug(name string, mask int32) { - names := []struct { - n string - m int32 - }{ - {"FILE_ACCESS", unix.FILE_ACCESS}, - {"FILE_MODIFIED", unix.FILE_MODIFIED}, - {"FILE_ATTRIB", unix.FILE_ATTRIB}, - {"FILE_TRUNC", unix.FILE_TRUNC}, - {"FILE_NOFOLLOW", unix.FILE_NOFOLLOW}, - {"FILE_DELETE", unix.FILE_DELETE}, - {"FILE_RENAME_TO", unix.FILE_RENAME_TO}, - {"FILE_RENAME_FROM", unix.FILE_RENAME_FROM}, - {"UNMOUNTED", unix.UNMOUNTED}, - {"MOUNTEDOVER", unix.MOUNTEDOVER}, - {"FILE_EXCEPTION", unix.FILE_EXCEPTION}, - } - - var ( - l []string - unknown = mask - ) - for _, n := range names { - if mask&n.m == n.m { - l = append(l, n.n) - unknown ^= n.m - } - } - if unknown > 0 { - l = append(l, fmt.Sprintf("0x%x", unknown)) - } - fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s %10d:%-30s → %q\n", - time.Now().Format("15:04:05.000000000"), mask, strings.Join(l, " | "), name) -} diff --git a/vendor/github.com/fsnotify/fsnotify/internal/debug_windows.go b/vendor/github.com/fsnotify/fsnotify/internal/debug_windows.go deleted file mode 100644 index 52bf4ce53..000000000 --- a/vendor/github.com/fsnotify/fsnotify/internal/debug_windows.go +++ /dev/null @@ -1,40 +0,0 @@ -package internal - -import ( - "fmt" - "os" - "path/filepath" - "strings" - "time" - - "golang.org/x/sys/windows" -) - -func Debug(name string, mask uint32) { - names := []struct { - n string - m uint32 - }{ - {"FILE_ACTION_ADDED", windows.FILE_ACTION_ADDED}, - {"FILE_ACTION_REMOVED", windows.FILE_ACTION_REMOVED}, - {"FILE_ACTION_MODIFIED", windows.FILE_ACTION_MODIFIED}, - {"FILE_ACTION_RENAMED_OLD_NAME", windows.FILE_ACTION_RENAMED_OLD_NAME}, - {"FILE_ACTION_RENAMED_NEW_NAME", windows.FILE_ACTION_RENAMED_NEW_NAME}, - } - - var ( - l []string - unknown = mask - ) - for _, n := range names { - if mask&n.m == n.m { - l = append(l, n.n) - unknown ^= n.m - } - } - if unknown > 0 { - l = append(l, fmt.Sprintf("0x%x", unknown)) - } - fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s %-65s → %q\n", - time.Now().Format("15:04:05.000000000"), strings.Join(l, " | "), filepath.ToSlash(name)) -} diff --git a/vendor/github.com/fsnotify/fsnotify/internal/freebsd.go b/vendor/github.com/fsnotify/fsnotify/internal/freebsd.go deleted file mode 100644 index 547df1df8..000000000 --- a/vendor/github.com/fsnotify/fsnotify/internal/freebsd.go +++ /dev/null @@ -1,31 +0,0 @@ -//go:build freebsd - -package internal - -import ( - "syscall" - - "golang.org/x/sys/unix" -) - -var ( - SyscallEACCES = syscall.EACCES - UnixEACCES = unix.EACCES -) - -var maxfiles uint64 - -func SetRlimit() { - // Go 1.19 will do this automatically: https://go-review.googlesource.com/c/go/+/393354/ - var l syscall.Rlimit - err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &l) - if err == nil && l.Cur != l.Max { - l.Cur = l.Max - syscall.Setrlimit(syscall.RLIMIT_NOFILE, &l) - } - maxfiles = uint64(l.Cur) -} - -func Maxfiles() uint64 { return maxfiles } -func Mkfifo(path string, mode uint32) error { return unix.Mkfifo(path, mode) } -func Mknod(path string, mode uint32, dev int) error { return unix.Mknod(path, mode, uint64(dev)) } diff --git a/vendor/github.com/fsnotify/fsnotify/internal/internal.go b/vendor/github.com/fsnotify/fsnotify/internal/internal.go deleted file mode 100644 index 7daa45e19..000000000 --- a/vendor/github.com/fsnotify/fsnotify/internal/internal.go +++ /dev/null @@ -1,2 +0,0 @@ -// Package internal contains some helpers. -package internal diff --git a/vendor/github.com/fsnotify/fsnotify/internal/unix.go b/vendor/github.com/fsnotify/fsnotify/internal/unix.go deleted file mode 100644 index 30976ce97..000000000 --- a/vendor/github.com/fsnotify/fsnotify/internal/unix.go +++ /dev/null @@ -1,31 +0,0 @@ -//go:build !windows && !darwin && !freebsd - -package internal - -import ( - "syscall" - - "golang.org/x/sys/unix" -) - -var ( - SyscallEACCES = syscall.EACCES - UnixEACCES = unix.EACCES -) - -var maxfiles uint64 - -func SetRlimit() { - // Go 1.19 will do this automatically: https://go-review.googlesource.com/c/go/+/393354/ - var l syscall.Rlimit - err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &l) - if err == nil && l.Cur != l.Max { - l.Cur = l.Max - syscall.Setrlimit(syscall.RLIMIT_NOFILE, &l) - } - maxfiles = uint64(l.Cur) -} - -func Maxfiles() uint64 { return maxfiles } -func Mkfifo(path string, mode uint32) error { return unix.Mkfifo(path, mode) } -func Mknod(path string, mode uint32, dev int) error { return unix.Mknod(path, mode, dev) } diff --git a/vendor/github.com/fsnotify/fsnotify/internal/unix2.go b/vendor/github.com/fsnotify/fsnotify/internal/unix2.go deleted file mode 100644 index 37dfeddc2..000000000 --- a/vendor/github.com/fsnotify/fsnotify/internal/unix2.go +++ /dev/null @@ -1,7 +0,0 @@ -//go:build !windows - -package internal - -func HasPrivilegesForSymlink() bool { - return true -} diff --git a/vendor/github.com/fsnotify/fsnotify/internal/windows.go b/vendor/github.com/fsnotify/fsnotify/internal/windows.go deleted file mode 100644 index a72c64954..000000000 --- a/vendor/github.com/fsnotify/fsnotify/internal/windows.go +++ /dev/null @@ -1,41 +0,0 @@ -//go:build windows - -package internal - -import ( - "errors" - - "golang.org/x/sys/windows" -) - -// Just a dummy. -var ( - SyscallEACCES = errors.New("dummy") - UnixEACCES = errors.New("dummy") -) - -func SetRlimit() {} -func Maxfiles() uint64 { return 1<<64 - 1 } -func Mkfifo(path string, mode uint32) error { return errors.New("no FIFOs on Windows") } -func Mknod(path string, mode uint32, dev int) error { return errors.New("no device nodes on Windows") } - -func HasPrivilegesForSymlink() bool { - var sid *windows.SID - err := windows.AllocateAndInitializeSid( - &windows.SECURITY_NT_AUTHORITY, - 2, - windows.SECURITY_BUILTIN_DOMAIN_RID, - windows.DOMAIN_ALIAS_RID_ADMINS, - 0, 0, 0, 0, 0, 0, - &sid) - if err != nil { - return false - } - defer windows.FreeSid(sid) - token := windows.Token(0) - member, err := token.IsMember(sid) - if err != nil { - return false - } - return member || token.IsElevated() -} diff --git a/vendor/github.com/fsnotify/fsnotify/mkdoc.zsh b/vendor/github.com/fsnotify/fsnotify/mkdoc.zsh new file mode 100644 index 000000000..99012ae65 --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/mkdoc.zsh @@ -0,0 +1,259 @@ +#!/usr/bin/env zsh +[ "${ZSH_VERSION:-}" = "" ] && echo >&2 "Only works with zsh" && exit 1 +setopt err_exit no_unset pipefail extended_glob + +# Simple script to update the godoc comments on all watchers so you don't need +# to update the same comment 5 times. + +watcher=$(</tmp/x + print -r -- $cmt >>/tmp/x + tail -n+$(( end + 1 )) $file >>/tmp/x + mv /tmp/x $file + done +} + +set-cmt '^type Watcher struct ' $watcher +set-cmt '^func NewWatcher(' $new +set-cmt '^func NewBufferedWatcher(' $newbuffered +set-cmt '^func (w \*Watcher) Add(' $add +set-cmt '^func (w \*Watcher) AddWith(' $addwith +set-cmt '^func (w \*Watcher) Remove(' $remove +set-cmt '^func (w \*Watcher) Close(' $close +set-cmt '^func (w \*Watcher) WatchList(' $watchlist +set-cmt '^[[:space:]]*Events *chan Event$' $events +set-cmt '^[[:space:]]*Errors *chan error$' $errors diff --git a/vendor/github.com/fsnotify/fsnotify/system_bsd.go b/vendor/github.com/fsnotify/fsnotify/system_bsd.go index f65e8fe3e..4322b0b88 100644 --- a/vendor/github.com/fsnotify/fsnotify/system_bsd.go +++ b/vendor/github.com/fsnotify/fsnotify/system_bsd.go @@ -1,4 +1,5 @@ //go:build freebsd || openbsd || netbsd || dragonfly +// +build freebsd openbsd netbsd dragonfly package fsnotify diff --git a/vendor/github.com/fsnotify/fsnotify/system_darwin.go b/vendor/github.com/fsnotify/fsnotify/system_darwin.go index a29fc7aab..5da5ffa78 100644 --- a/vendor/github.com/fsnotify/fsnotify/system_darwin.go +++ b/vendor/github.com/fsnotify/fsnotify/system_darwin.go @@ -1,4 +1,5 @@ //go:build darwin +// +build darwin package fsnotify diff --git a/vendor/golang.org/x/sys/unix/README.md b/vendor/golang.org/x/sys/unix/README.md index 6e08a76a7..7d3c060e1 100644 --- a/vendor/golang.org/x/sys/unix/README.md +++ b/vendor/golang.org/x/sys/unix/README.md @@ -156,7 +156,7 @@ from the generated architecture-specific files listed below, and merge these into a common file for each OS. The merge is performed in the following steps: -1. Construct the set of common code that is identical in all architecture-specific files. +1. Construct the set of common code that is idential in all architecture-specific files. 2. Write this common code to the merged file. 3. Remove the common code from all architecture-specific files. diff --git a/vendor/golang.org/x/sys/unix/mkerrors.sh b/vendor/golang.org/x/sys/unix/mkerrors.sh index ac54ecaba..e14b766a3 100644 --- a/vendor/golang.org/x/sys/unix/mkerrors.sh +++ b/vendor/golang.org/x/sys/unix/mkerrors.sh @@ -656,7 +656,7 @@ errors=$( signals=$( echo '#include ' | $CC -x c - -E -dM $ccflags | awk '$1=="#define" && $2 ~ /^SIG[A-Z0-9]+$/ { print $2 }' | - grep -E -v '(SIGSTKSIZE|SIGSTKSZ|SIGRT|SIGMAX64)' | + grep -v 'SIGSTKSIZE\|SIGSTKSZ\|SIGRT\|SIGMAX64' | sort ) @@ -666,7 +666,7 @@ echo '#include ' | $CC -x c - -E -dM $ccflags | sort >_error.grep echo '#include ' | $CC -x c - -E -dM $ccflags | awk '$1=="#define" && $2 ~ /^SIG[A-Z0-9]+$/ { print "^\t" $2 "[ \t]*=" }' | - grep -E -v '(SIGSTKSIZE|SIGSTKSZ|SIGRT|SIGMAX64)' | + grep -v 'SIGSTKSIZE\|SIGSTKSZ\|SIGRT\|SIGMAX64' | sort >_signal.grep echo '// mkerrors.sh' "$@" diff --git a/vendor/golang.org/x/sys/unix/syscall_aix.go b/vendor/golang.org/x/sys/unix/syscall_aix.go index 6f15ba1ea..67ce6cef2 100644 --- a/vendor/golang.org/x/sys/unix/syscall_aix.go +++ b/vendor/golang.org/x/sys/unix/syscall_aix.go @@ -360,7 +360,7 @@ func Wait4(pid int, wstatus *WaitStatus, options int, rusage *Rusage) (wpid int, var status _C_int var r Pid_t err = ERESTART - // AIX wait4 may return with ERESTART errno, while the process is still + // AIX wait4 may return with ERESTART errno, while the processus is still // active. for err == ERESTART { r, err = wait4(Pid_t(pid), &status, options, rusage) diff --git a/vendor/golang.org/x/sys/unix/syscall_linux.go b/vendor/golang.org/x/sys/unix/syscall_linux.go index f08abd434..3f1d3d4cb 100644 --- a/vendor/golang.org/x/sys/unix/syscall_linux.go +++ b/vendor/golang.org/x/sys/unix/syscall_linux.go @@ -1295,48 +1295,6 @@ func GetsockoptTCPInfo(fd, level, opt int) (*TCPInfo, error) { return &value, err } -// GetsockoptTCPCCVegasInfo returns algorithm specific congestion control information for a socket using the "vegas" -// algorithm. -// -// The socket's congestion control algorighm can be retrieved via [GetsockoptString] with the [TCP_CONGESTION] option: -// -// algo, err := unix.GetsockoptString(fd, unix.IPPROTO_TCP, unix.TCP_CONGESTION) -func GetsockoptTCPCCVegasInfo(fd, level, opt int) (*TCPVegasInfo, error) { - var value [SizeofTCPCCInfo / 4]uint32 // ensure proper alignment - vallen := _Socklen(SizeofTCPCCInfo) - err := getsockopt(fd, level, opt, unsafe.Pointer(&value[0]), &vallen) - out := (*TCPVegasInfo)(unsafe.Pointer(&value[0])) - return out, err -} - -// GetsockoptTCPCCDCTCPInfo returns algorithm specific congestion control information for a socket using the "dctp" -// algorithm. -// -// The socket's congestion control algorighm can be retrieved via [GetsockoptString] with the [TCP_CONGESTION] option: -// -// algo, err := unix.GetsockoptString(fd, unix.IPPROTO_TCP, unix.TCP_CONGESTION) -func GetsockoptTCPCCDCTCPInfo(fd, level, opt int) (*TCPDCTCPInfo, error) { - var value [SizeofTCPCCInfo / 4]uint32 // ensure proper alignment - vallen := _Socklen(SizeofTCPCCInfo) - err := getsockopt(fd, level, opt, unsafe.Pointer(&value[0]), &vallen) - out := (*TCPDCTCPInfo)(unsafe.Pointer(&value[0])) - return out, err -} - -// GetsockoptTCPCCBBRInfo returns algorithm specific congestion control information for a socket using the "bbr" -// algorithm. -// -// The socket's congestion control algorighm can be retrieved via [GetsockoptString] with the [TCP_CONGESTION] option: -// -// algo, err := unix.GetsockoptString(fd, unix.IPPROTO_TCP, unix.TCP_CONGESTION) -func GetsockoptTCPCCBBRInfo(fd, level, opt int) (*TCPBBRInfo, error) { - var value [SizeofTCPCCInfo / 4]uint32 // ensure proper alignment - vallen := _Socklen(SizeofTCPCCInfo) - err := getsockopt(fd, level, opt, unsafe.Pointer(&value[0]), &vallen) - out := (*TCPBBRInfo)(unsafe.Pointer(&value[0])) - return out, err -} - // GetsockoptString returns the string value of the socket option opt for the // socket associated with fd at the given socket level. func GetsockoptString(fd, level, opt int) (string, error) { @@ -2001,26 +1959,7 @@ func Getpgrp() (pid int) { //sysnb Getpid() (pid int) //sysnb Getppid() (ppid int) //sys Getpriority(which int, who int) (prio int, err error) - -func Getrandom(buf []byte, flags int) (n int, err error) { - vdsoRet, supported := vgetrandom(buf, uint32(flags)) - if supported { - if vdsoRet < 0 { - return 0, errnoErr(syscall.Errno(-vdsoRet)) - } - return vdsoRet, nil - } - var p *byte - if len(buf) > 0 { - p = &buf[0] - } - r, _, e := Syscall(SYS_GETRANDOM, uintptr(unsafe.Pointer(p)), uintptr(len(buf)), uintptr(flags)) - if e != 0 { - return 0, errnoErr(e) - } - return int(r), nil -} - +//sys Getrandom(buf []byte, flags int) (n int, err error) //sysnb Getrusage(who int, rusage *Rusage) (err error) //sysnb Getsid(pid int) (sid int, err error) //sysnb Gettid() (tid int) diff --git a/vendor/golang.org/x/sys/unix/syscall_linux_arm64.go b/vendor/golang.org/x/sys/unix/syscall_linux_arm64.go index 745e5c7e6..cf2ee6c75 100644 --- a/vendor/golang.org/x/sys/unix/syscall_linux_arm64.go +++ b/vendor/golang.org/x/sys/unix/syscall_linux_arm64.go @@ -182,5 +182,3 @@ func KexecFileLoad(kernelFd int, initrdFd int, cmdline string, flags int) error } return kexecFileLoad(kernelFd, initrdFd, cmdlineLen, cmdline, flags) } - -const SYS_FSTATAT = SYS_NEWFSTATAT diff --git a/vendor/golang.org/x/sys/unix/syscall_linux_loong64.go b/vendor/golang.org/x/sys/unix/syscall_linux_loong64.go index dd2262a40..3d0e98451 100644 --- a/vendor/golang.org/x/sys/unix/syscall_linux_loong64.go +++ b/vendor/golang.org/x/sys/unix/syscall_linux_loong64.go @@ -214,5 +214,3 @@ func KexecFileLoad(kernelFd int, initrdFd int, cmdline string, flags int) error } return kexecFileLoad(kernelFd, initrdFd, cmdlineLen, cmdline, flags) } - -const SYS_FSTATAT = SYS_NEWFSTATAT diff --git a/vendor/golang.org/x/sys/unix/syscall_linux_riscv64.go b/vendor/golang.org/x/sys/unix/syscall_linux_riscv64.go index 8cf3670bd..6f5a28894 100644 --- a/vendor/golang.org/x/sys/unix/syscall_linux_riscv64.go +++ b/vendor/golang.org/x/sys/unix/syscall_linux_riscv64.go @@ -187,5 +187,3 @@ func RISCVHWProbe(pairs []RISCVHWProbePairs, set *CPUSet, flags uint) (err error } return riscvHWProbe(pairs, setSize, set, flags) } - -const SYS_FSTATAT = SYS_NEWFSTATAT diff --git a/vendor/golang.org/x/sys/unix/vgetrandom_linux.go b/vendor/golang.org/x/sys/unix/vgetrandom_linux.go deleted file mode 100644 index 07ac8e09d..000000000 --- a/vendor/golang.org/x/sys/unix/vgetrandom_linux.go +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright 2024 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build linux && go1.24 - -package unix - -import _ "unsafe" - -//go:linkname vgetrandom runtime.vgetrandom -//go:noescape -func vgetrandom(p []byte, flags uint32) (ret int, supported bool) diff --git a/vendor/golang.org/x/sys/unix/vgetrandom_unsupported.go b/vendor/golang.org/x/sys/unix/vgetrandom_unsupported.go deleted file mode 100644 index 297e97bce..000000000 --- a/vendor/golang.org/x/sys/unix/vgetrandom_unsupported.go +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright 2024 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build !linux || !go1.24 - -package unix - -func vgetrandom(p []byte, flags uint32) (ret int, supported bool) { - return -1, false -} diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux.go b/vendor/golang.org/x/sys/unix/zerrors_linux.go index de3b46248..01a70b246 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux.go @@ -495,7 +495,6 @@ const ( BPF_F_TEST_REG_INVARIANTS = 0x80 BPF_F_TEST_RND_HI32 = 0x4 BPF_F_TEST_RUN_ON_CPU = 0x1 - BPF_F_TEST_SKB_CHECKSUM_COMPLETE = 0x4 BPF_F_TEST_STATE_FREQ = 0x8 BPF_F_TEST_XDP_LIVE_FRAMES = 0x2 BPF_F_XDP_DEV_BOUND_ONLY = 0x40 @@ -1923,7 +1922,6 @@ const ( MNT_EXPIRE = 0x4 MNT_FORCE = 0x1 MNT_ID_REQ_SIZE_VER0 = 0x18 - MNT_ID_REQ_SIZE_VER1 = 0x20 MODULE_INIT_COMPRESSED_FILE = 0x4 MODULE_INIT_IGNORE_MODVERSIONS = 0x1 MODULE_INIT_IGNORE_VERMAGIC = 0x2 @@ -2189,7 +2187,7 @@ const ( NFT_REG_SIZE = 0x10 NFT_REJECT_ICMPX_MAX = 0x3 NFT_RT_MAX = 0x4 - NFT_SECMARK_CTX_MAXLEN = 0x1000 + NFT_SECMARK_CTX_MAXLEN = 0x100 NFT_SET_MAXNAMELEN = 0x100 NFT_SOCKET_MAX = 0x3 NFT_TABLE_F_MASK = 0x7 @@ -2358,11 +2356,9 @@ const ( PERF_MEM_LVLNUM_IO = 0xa PERF_MEM_LVLNUM_L1 = 0x1 PERF_MEM_LVLNUM_L2 = 0x2 - PERF_MEM_LVLNUM_L2_MHB = 0x5 PERF_MEM_LVLNUM_L3 = 0x3 PERF_MEM_LVLNUM_L4 = 0x4 PERF_MEM_LVLNUM_LFB = 0xc - PERF_MEM_LVLNUM_MSC = 0x6 PERF_MEM_LVLNUM_NA = 0xf PERF_MEM_LVLNUM_PMEM = 0xe PERF_MEM_LVLNUM_RAM = 0xd @@ -2435,7 +2431,6 @@ const ( PRIO_PGRP = 0x1 PRIO_PROCESS = 0x0 PRIO_USER = 0x2 - PROCFS_IOCTL_MAGIC = 'f' PROC_SUPER_MAGIC = 0x9fa0 PROT_EXEC = 0x4 PROT_GROWSDOWN = 0x1000000 @@ -2938,12 +2933,11 @@ const ( RUSAGE_SELF = 0x0 RUSAGE_THREAD = 0x1 RWF_APPEND = 0x10 - RWF_ATOMIC = 0x40 RWF_DSYNC = 0x2 RWF_HIPRI = 0x1 RWF_NOAPPEND = 0x20 RWF_NOWAIT = 0x8 - RWF_SUPPORTED = 0x7f + RWF_SUPPORTED = 0x3f RWF_SYNC = 0x4 RWF_WRITE_LIFE_NOT_SET = 0x0 SCHED_BATCH = 0x3 @@ -3216,7 +3210,6 @@ const ( STATX_ATTR_MOUNT_ROOT = 0x2000 STATX_ATTR_NODUMP = 0x40 STATX_ATTR_VERITY = 0x100000 - STATX_ATTR_WRITE_ATOMIC = 0x400000 STATX_BASIC_STATS = 0x7ff STATX_BLOCKS = 0x400 STATX_BTIME = 0x800 @@ -3233,7 +3226,6 @@ const ( STATX_SUBVOL = 0x8000 STATX_TYPE = 0x1 STATX_UID = 0x8 - STATX_WRITE_ATOMIC = 0x10000 STATX__RESERVED = 0x80000000 SYNC_FILE_RANGE_WAIT_AFTER = 0x4 SYNC_FILE_RANGE_WAIT_BEFORE = 0x1 @@ -3632,7 +3624,6 @@ const ( XDP_UMEM_PGOFF_COMPLETION_RING = 0x180000000 XDP_UMEM_PGOFF_FILL_RING = 0x100000000 XDP_UMEM_REG = 0x4 - XDP_UMEM_TX_METADATA_LEN = 0x4 XDP_UMEM_TX_SW_CSUM = 0x2 XDP_UMEM_UNALIGNED_CHUNK_FLAG = 0x1 XDP_USE_NEED_WAKEUP = 0x8 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_386.go b/vendor/golang.org/x/sys/unix/zerrors_linux_386.go index 8aa6d77c0..684a5168d 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_386.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_386.go @@ -153,14 +153,9 @@ const ( NFDBITS = 0x20 NLDLY = 0x100 NOFLSH = 0x80 - NS_GET_MNTNS_ID = 0x8008b705 NS_GET_NSTYPE = 0xb703 NS_GET_OWNER_UID = 0xb704 NS_GET_PARENT = 0xb702 - NS_GET_PID_FROM_PIDNS = 0x8004b706 - NS_GET_PID_IN_PIDNS = 0x8004b708 - NS_GET_TGID_FROM_PIDNS = 0x8004b707 - NS_GET_TGID_IN_PIDNS = 0x8004b709 NS_GET_USERNS = 0xb701 OLCUC = 0x2 ONLCR = 0x4 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_amd64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_amd64.go index da428f425..61d74b592 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_amd64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_amd64.go @@ -153,14 +153,9 @@ const ( NFDBITS = 0x40 NLDLY = 0x100 NOFLSH = 0x80 - NS_GET_MNTNS_ID = 0x8008b705 NS_GET_NSTYPE = 0xb703 NS_GET_OWNER_UID = 0xb704 NS_GET_PARENT = 0xb702 - NS_GET_PID_FROM_PIDNS = 0x8004b706 - NS_GET_PID_IN_PIDNS = 0x8004b708 - NS_GET_TGID_FROM_PIDNS = 0x8004b707 - NS_GET_TGID_IN_PIDNS = 0x8004b709 NS_GET_USERNS = 0xb701 OLCUC = 0x2 ONLCR = 0x4 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_arm.go b/vendor/golang.org/x/sys/unix/zerrors_linux_arm.go index bf45bfec7..a28c9e3e8 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_arm.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_arm.go @@ -150,14 +150,9 @@ const ( NFDBITS = 0x20 NLDLY = 0x100 NOFLSH = 0x80 - NS_GET_MNTNS_ID = 0x8008b705 NS_GET_NSTYPE = 0xb703 NS_GET_OWNER_UID = 0xb704 NS_GET_PARENT = 0xb702 - NS_GET_PID_FROM_PIDNS = 0x8004b706 - NS_GET_PID_IN_PIDNS = 0x8004b708 - NS_GET_TGID_FROM_PIDNS = 0x8004b707 - NS_GET_TGID_IN_PIDNS = 0x8004b709 NS_GET_USERNS = 0xb701 OLCUC = 0x2 ONLCR = 0x4 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_arm64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_arm64.go index 71c67162b..ab5d1fe8e 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_arm64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_arm64.go @@ -154,14 +154,9 @@ const ( NFDBITS = 0x40 NLDLY = 0x100 NOFLSH = 0x80 - NS_GET_MNTNS_ID = 0x8008b705 NS_GET_NSTYPE = 0xb703 NS_GET_OWNER_UID = 0xb704 NS_GET_PARENT = 0xb702 - NS_GET_PID_FROM_PIDNS = 0x8004b706 - NS_GET_PID_IN_PIDNS = 0x8004b708 - NS_GET_TGID_FROM_PIDNS = 0x8004b707 - NS_GET_TGID_IN_PIDNS = 0x8004b709 NS_GET_USERNS = 0xb701 OLCUC = 0x2 ONLCR = 0x4 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_loong64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_loong64.go index 9476628fa..c523090e7 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_loong64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_loong64.go @@ -154,14 +154,9 @@ const ( NFDBITS = 0x40 NLDLY = 0x100 NOFLSH = 0x80 - NS_GET_MNTNS_ID = 0x8008b705 NS_GET_NSTYPE = 0xb703 NS_GET_OWNER_UID = 0xb704 NS_GET_PARENT = 0xb702 - NS_GET_PID_FROM_PIDNS = 0x8004b706 - NS_GET_PID_IN_PIDNS = 0x8004b708 - NS_GET_TGID_FROM_PIDNS = 0x8004b707 - NS_GET_TGID_IN_PIDNS = 0x8004b709 NS_GET_USERNS = 0xb701 OLCUC = 0x2 ONLCR = 0x4 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_mips.go b/vendor/golang.org/x/sys/unix/zerrors_linux_mips.go index b9e85f3cf..01e6ea780 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_mips.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_mips.go @@ -150,14 +150,9 @@ const ( NFDBITS = 0x20 NLDLY = 0x100 NOFLSH = 0x80 - NS_GET_MNTNS_ID = 0x4008b705 NS_GET_NSTYPE = 0x2000b703 NS_GET_OWNER_UID = 0x2000b704 NS_GET_PARENT = 0x2000b702 - NS_GET_PID_FROM_PIDNS = 0x4004b706 - NS_GET_PID_IN_PIDNS = 0x4004b708 - NS_GET_TGID_FROM_PIDNS = 0x4004b707 - NS_GET_TGID_IN_PIDNS = 0x4004b709 NS_GET_USERNS = 0x2000b701 OLCUC = 0x2 ONLCR = 0x4 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_mips64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_mips64.go index a48b68a76..7aa610b1e 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_mips64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_mips64.go @@ -150,14 +150,9 @@ const ( NFDBITS = 0x40 NLDLY = 0x100 NOFLSH = 0x80 - NS_GET_MNTNS_ID = 0x4008b705 NS_GET_NSTYPE = 0x2000b703 NS_GET_OWNER_UID = 0x2000b704 NS_GET_PARENT = 0x2000b702 - NS_GET_PID_FROM_PIDNS = 0x4004b706 - NS_GET_PID_IN_PIDNS = 0x4004b708 - NS_GET_TGID_FROM_PIDNS = 0x4004b707 - NS_GET_TGID_IN_PIDNS = 0x4004b709 NS_GET_USERNS = 0x2000b701 OLCUC = 0x2 ONLCR = 0x4 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_mips64le.go b/vendor/golang.org/x/sys/unix/zerrors_linux_mips64le.go index ea00e8522..92af771b4 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_mips64le.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_mips64le.go @@ -150,14 +150,9 @@ const ( NFDBITS = 0x40 NLDLY = 0x100 NOFLSH = 0x80 - NS_GET_MNTNS_ID = 0x4008b705 NS_GET_NSTYPE = 0x2000b703 NS_GET_OWNER_UID = 0x2000b704 NS_GET_PARENT = 0x2000b702 - NS_GET_PID_FROM_PIDNS = 0x4004b706 - NS_GET_PID_IN_PIDNS = 0x4004b708 - NS_GET_TGID_FROM_PIDNS = 0x4004b707 - NS_GET_TGID_IN_PIDNS = 0x4004b709 NS_GET_USERNS = 0x2000b701 OLCUC = 0x2 ONLCR = 0x4 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_mipsle.go b/vendor/golang.org/x/sys/unix/zerrors_linux_mipsle.go index 91c646871..b27ef5e6f 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_mipsle.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_mipsle.go @@ -150,14 +150,9 @@ const ( NFDBITS = 0x20 NLDLY = 0x100 NOFLSH = 0x80 - NS_GET_MNTNS_ID = 0x4008b705 NS_GET_NSTYPE = 0x2000b703 NS_GET_OWNER_UID = 0x2000b704 NS_GET_PARENT = 0x2000b702 - NS_GET_PID_FROM_PIDNS = 0x4004b706 - NS_GET_PID_IN_PIDNS = 0x4004b708 - NS_GET_TGID_FROM_PIDNS = 0x4004b707 - NS_GET_TGID_IN_PIDNS = 0x4004b709 NS_GET_USERNS = 0x2000b701 OLCUC = 0x2 ONLCR = 0x4 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_ppc.go b/vendor/golang.org/x/sys/unix/zerrors_linux_ppc.go index 8cbf38d63..237a2cefb 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_ppc.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_ppc.go @@ -152,14 +152,9 @@ const ( NL3 = 0x300 NLDLY = 0x300 NOFLSH = 0x80000000 - NS_GET_MNTNS_ID = 0x4008b705 NS_GET_NSTYPE = 0x2000b703 NS_GET_OWNER_UID = 0x2000b704 NS_GET_PARENT = 0x2000b702 - NS_GET_PID_FROM_PIDNS = 0x4004b706 - NS_GET_PID_IN_PIDNS = 0x4004b708 - NS_GET_TGID_FROM_PIDNS = 0x4004b707 - NS_GET_TGID_IN_PIDNS = 0x4004b709 NS_GET_USERNS = 0x2000b701 OLCUC = 0x4 ONLCR = 0x2 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64.go index a2df73419..4a5c555a3 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64.go @@ -152,14 +152,9 @@ const ( NL3 = 0x300 NLDLY = 0x300 NOFLSH = 0x80000000 - NS_GET_MNTNS_ID = 0x4008b705 NS_GET_NSTYPE = 0x2000b703 NS_GET_OWNER_UID = 0x2000b704 NS_GET_PARENT = 0x2000b702 - NS_GET_PID_FROM_PIDNS = 0x4004b706 - NS_GET_PID_IN_PIDNS = 0x4004b708 - NS_GET_TGID_FROM_PIDNS = 0x4004b707 - NS_GET_TGID_IN_PIDNS = 0x4004b709 NS_GET_USERNS = 0x2000b701 OLCUC = 0x4 ONLCR = 0x2 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64le.go b/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64le.go index 247913792..a02fb49a5 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64le.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64le.go @@ -152,14 +152,9 @@ const ( NL3 = 0x300 NLDLY = 0x300 NOFLSH = 0x80000000 - NS_GET_MNTNS_ID = 0x4008b705 NS_GET_NSTYPE = 0x2000b703 NS_GET_OWNER_UID = 0x2000b704 NS_GET_PARENT = 0x2000b702 - NS_GET_PID_FROM_PIDNS = 0x4004b706 - NS_GET_PID_IN_PIDNS = 0x4004b708 - NS_GET_TGID_FROM_PIDNS = 0x4004b707 - NS_GET_TGID_IN_PIDNS = 0x4004b709 NS_GET_USERNS = 0x2000b701 OLCUC = 0x4 ONLCR = 0x2 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_riscv64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_riscv64.go index d265f146e..e26a7c61b 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_riscv64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_riscv64.go @@ -150,14 +150,9 @@ const ( NFDBITS = 0x40 NLDLY = 0x100 NOFLSH = 0x80 - NS_GET_MNTNS_ID = 0x8008b705 NS_GET_NSTYPE = 0xb703 NS_GET_OWNER_UID = 0xb704 NS_GET_PARENT = 0xb702 - NS_GET_PID_FROM_PIDNS = 0x8004b706 - NS_GET_PID_IN_PIDNS = 0x8004b708 - NS_GET_TGID_FROM_PIDNS = 0x8004b707 - NS_GET_TGID_IN_PIDNS = 0x8004b709 NS_GET_USERNS = 0xb701 OLCUC = 0x2 ONLCR = 0x4 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_s390x.go b/vendor/golang.org/x/sys/unix/zerrors_linux_s390x.go index 3f2d64439..c48f7c210 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_s390x.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_s390x.go @@ -150,14 +150,9 @@ const ( NFDBITS = 0x40 NLDLY = 0x100 NOFLSH = 0x80 - NS_GET_MNTNS_ID = 0x8008b705 NS_GET_NSTYPE = 0xb703 NS_GET_OWNER_UID = 0xb704 NS_GET_PARENT = 0xb702 - NS_GET_PID_FROM_PIDNS = 0x8004b706 - NS_GET_PID_IN_PIDNS = 0x8004b708 - NS_GET_TGID_FROM_PIDNS = 0x8004b707 - NS_GET_TGID_IN_PIDNS = 0x8004b709 NS_GET_USERNS = 0xb701 OLCUC = 0x2 ONLCR = 0x4 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_sparc64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_sparc64.go index 5d8b727a1..ad4b9aace 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_sparc64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_sparc64.go @@ -155,14 +155,9 @@ const ( NFDBITS = 0x40 NLDLY = 0x100 NOFLSH = 0x80 - NS_GET_MNTNS_ID = 0x4008b705 NS_GET_NSTYPE = 0x2000b703 NS_GET_OWNER_UID = 0x2000b704 NS_GET_PARENT = 0x2000b702 - NS_GET_PID_FROM_PIDNS = 0x4004b706 - NS_GET_PID_IN_PIDNS = 0x4004b708 - NS_GET_TGID_FROM_PIDNS = 0x4004b707 - NS_GET_TGID_IN_PIDNS = 0x4004b709 NS_GET_USERNS = 0x2000b701 OLCUC = 0x2 ONLCR = 0x4 diff --git a/vendor/golang.org/x/sys/unix/zsyscall_linux.go b/vendor/golang.org/x/sys/unix/zsyscall_linux.go index af30da557..1bc1a5adb 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_linux.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_linux.go @@ -971,6 +971,23 @@ func Getpriority(which int, who int) (prio int, err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT +func Getrandom(buf []byte, flags int) (n int, err error) { + var _p0 unsafe.Pointer + if len(buf) > 0 { + _p0 = unsafe.Pointer(&buf[0]) + } else { + _p0 = unsafe.Pointer(&_zero) + } + r0, _, e1 := Syscall(SYS_GETRANDOM, uintptr(_p0), uintptr(len(buf)), uintptr(flags)) + n = int(r0) + if e1 != 0 { + err = errnoErr(e1) + } + return +} + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + func Getrusage(who int, rusage *Rusage) (err error) { _, _, e1 := RawSyscall(SYS_GETRUSAGE, uintptr(who), uintptr(unsafe.Pointer(rusage)), 0) if e1 != 0 { diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_amd64.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_amd64.go index f485dbf45..d3e38f681 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_amd64.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_amd64.go @@ -341,7 +341,6 @@ const ( SYS_STATX = 332 SYS_IO_PGETEVENTS = 333 SYS_RSEQ = 334 - SYS_URETPROBE = 335 SYS_PIDFD_SEND_SIGNAL = 424 SYS_IO_URING_SETUP = 425 SYS_IO_URING_ENTER = 426 diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_arm64.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_arm64.go index 1893e2fe8..6c778c232 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_arm64.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_arm64.go @@ -85,7 +85,7 @@ const ( SYS_SPLICE = 76 SYS_TEE = 77 SYS_READLINKAT = 78 - SYS_NEWFSTATAT = 79 + SYS_FSTATAT = 79 SYS_FSTAT = 80 SYS_SYNC = 81 SYS_FSYNC = 82 diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_loong64.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_loong64.go index 16a4017da..37281cf51 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_loong64.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_loong64.go @@ -84,8 +84,6 @@ const ( SYS_SPLICE = 76 SYS_TEE = 77 SYS_READLINKAT = 78 - SYS_NEWFSTATAT = 79 - SYS_FSTAT = 80 SYS_SYNC = 81 SYS_FSYNC = 82 SYS_FDATASYNC = 83 diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_riscv64.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_riscv64.go index a5459e766..9889f6a55 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_riscv64.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_riscv64.go @@ -84,7 +84,7 @@ const ( SYS_SPLICE = 76 SYS_TEE = 77 SYS_READLINKAT = 78 - SYS_NEWFSTATAT = 79 + SYS_FSTATAT = 79 SYS_FSTAT = 80 SYS_SYNC = 81 SYS_FSYNC = 82 diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux.go b/vendor/golang.org/x/sys/unix/ztypes_linux.go index 3a69e4549..9f2550dc3 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux.go @@ -87,35 +87,31 @@ type StatxTimestamp struct { } type Statx_t struct { - Mask uint32 - Blksize uint32 - Attributes uint64 - Nlink uint32 - Uid uint32 - Gid uint32 - Mode uint16 - _ [1]uint16 - Ino uint64 - Size uint64 - Blocks uint64 - Attributes_mask uint64 - Atime StatxTimestamp - Btime StatxTimestamp - Ctime StatxTimestamp - Mtime StatxTimestamp - Rdev_major uint32 - Rdev_minor uint32 - Dev_major uint32 - Dev_minor uint32 - Mnt_id uint64 - Dio_mem_align uint32 - Dio_offset_align uint32 - Subvol uint64 - Atomic_write_unit_min uint32 - Atomic_write_unit_max uint32 - Atomic_write_segments_max uint32 - _ [1]uint32 - _ [9]uint64 + Mask uint32 + Blksize uint32 + Attributes uint64 + Nlink uint32 + Uid uint32 + Gid uint32 + Mode uint16 + _ [1]uint16 + Ino uint64 + Size uint64 + Blocks uint64 + Attributes_mask uint64 + Atime StatxTimestamp + Btime StatxTimestamp + Ctime StatxTimestamp + Mtime StatxTimestamp + Rdev_major uint32 + Rdev_minor uint32 + Dev_major uint32 + Dev_minor uint32 + Mnt_id uint64 + Dio_mem_align uint32 + Dio_offset_align uint32 + Subvol uint64 + _ [11]uint64 } type Fsid struct { @@ -520,29 +516,6 @@ type TCPInfo struct { Total_rto_time uint32 } -type TCPVegasInfo struct { - Enabled uint32 - Rttcnt uint32 - Rtt uint32 - Minrtt uint32 -} - -type TCPDCTCPInfo struct { - Enabled uint16 - Ce_state uint16 - Alpha uint32 - Ab_ecn uint32 - Ab_tot uint32 -} - -type TCPBBRInfo struct { - Bw_lo uint32 - Bw_hi uint32 - Min_rtt uint32 - Pacing_gain uint32 - Cwnd_gain uint32 -} - type CanFilter struct { Id uint32 Mask uint32 @@ -584,7 +557,6 @@ const ( SizeofICMPv6Filter = 0x20 SizeofUcred = 0xc SizeofTCPInfo = 0xf8 - SizeofTCPCCInfo = 0x14 SizeofCanFilter = 0x8 SizeofTCPRepairOpt = 0x8 ) @@ -3794,7 +3766,7 @@ const ( ETHTOOL_MSG_PSE_GET = 0x24 ETHTOOL_MSG_PSE_SET = 0x25 ETHTOOL_MSG_RSS_GET = 0x26 - ETHTOOL_MSG_USER_MAX = 0x2c + ETHTOOL_MSG_USER_MAX = 0x2b ETHTOOL_MSG_KERNEL_NONE = 0x0 ETHTOOL_MSG_STRSET_GET_REPLY = 0x1 ETHTOOL_MSG_LINKINFO_GET_REPLY = 0x2 @@ -3834,7 +3806,7 @@ const ( ETHTOOL_MSG_MODULE_NTF = 0x24 ETHTOOL_MSG_PSE_GET_REPLY = 0x25 ETHTOOL_MSG_RSS_GET_REPLY = 0x26 - ETHTOOL_MSG_KERNEL_MAX = 0x2c + ETHTOOL_MSG_KERNEL_MAX = 0x2b ETHTOOL_FLAG_COMPACT_BITSETS = 0x1 ETHTOOL_FLAG_OMIT_REPLY = 0x2 ETHTOOL_FLAG_STATS = 0x4 @@ -3979,7 +3951,7 @@ const ( ETHTOOL_A_COALESCE_RATE_SAMPLE_INTERVAL = 0x17 ETHTOOL_A_COALESCE_USE_CQE_MODE_TX = 0x18 ETHTOOL_A_COALESCE_USE_CQE_MODE_RX = 0x19 - ETHTOOL_A_COALESCE_MAX = 0x1e + ETHTOOL_A_COALESCE_MAX = 0x1c ETHTOOL_A_PAUSE_UNSPEC = 0x0 ETHTOOL_A_PAUSE_HEADER = 0x1 ETHTOOL_A_PAUSE_AUTONEG = 0x2 @@ -4637,7 +4609,7 @@ const ( NL80211_ATTR_MAC_HINT = 0xc8 NL80211_ATTR_MAC_MASK = 0xd7 NL80211_ATTR_MAX_AP_ASSOC_STA = 0xca - NL80211_ATTR_MAX = 0x14c + NL80211_ATTR_MAX = 0x14a NL80211_ATTR_MAX_CRIT_PROT_DURATION = 0xb4 NL80211_ATTR_MAX_CSA_COUNTERS = 0xce NL80211_ATTR_MAX_MATCH_SETS = 0x85 @@ -5241,7 +5213,7 @@ const ( NL80211_FREQUENCY_ATTR_GO_CONCURRENT = 0xf NL80211_FREQUENCY_ATTR_INDOOR_ONLY = 0xe NL80211_FREQUENCY_ATTR_IR_CONCURRENT = 0xf - NL80211_FREQUENCY_ATTR_MAX = 0x21 + NL80211_FREQUENCY_ATTR_MAX = 0x20 NL80211_FREQUENCY_ATTR_MAX_TX_POWER = 0x6 NL80211_FREQUENCY_ATTR_NO_10MHZ = 0x11 NL80211_FREQUENCY_ATTR_NO_160MHZ = 0xc diff --git a/vendor/golang.org/x/sys/windows/dll_windows.go b/vendor/golang.org/x/sys/windows/dll_windows.go index 4e613cf63..115341fba 100644 --- a/vendor/golang.org/x/sys/windows/dll_windows.go +++ b/vendor/golang.org/x/sys/windows/dll_windows.go @@ -65,7 +65,7 @@ func LoadDLL(name string) (dll *DLL, err error) { return d, nil } -// MustLoadDLL is like LoadDLL but panics if load operation fails. +// MustLoadDLL is like LoadDLL but panics if load operation failes. func MustLoadDLL(name string) *DLL { d, e := LoadDLL(name) if e != nil { diff --git a/vendor/modules.txt b/vendor/modules.txt index e294e48ae..917fee68a 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -12,10 +12,9 @@ github.com/bazelbuild/rules_go/go/tools/internal/txtar # github.com/bmatcuk/doublestar/v4 v4.7.1 ## explicit; go 1.16 github.com/bmatcuk/doublestar/v4 -# github.com/fsnotify/fsnotify v1.8.0 +# github.com/fsnotify/fsnotify v1.7.0 ## explicit; go 1.17 github.com/fsnotify/fsnotify -github.com/fsnotify/fsnotify/internal # github.com/google/go-cmp v0.6.0 ## explicit; go 1.13 github.com/google/go-cmp/cmp @@ -26,8 +25,8 @@ github.com/google/go-cmp/cmp/internal/value # github.com/pmezard/go-difflib v1.0.0 ## explicit github.com/pmezard/go-difflib/difflib -# golang.org/x/mod v0.21.0 -## explicit; go 1.22.0 +# golang.org/x/mod v0.20.0 +## explicit; go 1.18 golang.org/x/mod/internal/lazyregexp golang.org/x/mod/modfile golang.org/x/mod/module @@ -36,7 +35,7 @@ golang.org/x/mod/sumdb/dirhash # golang.org/x/sync v0.8.0 ## explicit; go 1.18 golang.org/x/sync/errgroup -# golang.org/x/sys v0.26.0 +# golang.org/x/sys v0.25.0 ## explicit; go 1.18 golang.org/x/sys/execabs golang.org/x/sys/unix From ae12fa635f6d4b8e5ef3fdcb4ac7fe4ce3f4748e Mon Sep 17 00:00:00 2001 From: Holger Freyther Date: Wed, 12 Feb 2025 21:13:50 +0800 Subject: [PATCH 06/27] prometheus: Add directives needed for prometheus and alertmanager (#2032) This is enough to build promtool, prometheus, amtool and alertmanager using bazel. The code includes generated gogoproto code and requires to disable proto handling. **What type of PR is this?** > Other **What package or component does this PR mostly affect?** internal/bzlmod **What does this PR do? Why is it needed?** Prometheus and Alertmanager have generated gogoproto code that needs to be built. This makes it easy to use amtool/promtool from non-root modules. **Which issues(s) does this PR fix?** **Other notes for review** --- internal/bzlmod/default_gazelle_overrides.bzl | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/internal/bzlmod/default_gazelle_overrides.bzl b/internal/bzlmod/default_gazelle_overrides.bzl index 2f4b9dbba..3c4a56370 100644 --- a/internal/bzlmod/default_gazelle_overrides.bzl +++ b/internal/bzlmod/default_gazelle_overrides.bzl @@ -85,6 +85,12 @@ DEFAULT_DIRECTIVES_BY_PATH = { "gazelle:go_naming_convention import_alias", "gazelle:proto disable", ], + "github.com/prometheus/alertmanager": [ + "gazelle:proto disable", + ], + "github.com/prometheus/prometheus": [ + "gazelle:proto disable", + ], "github.com/pseudomuto/protoc-gen-doc": [ # The build file in github.com/mwitkow/go-proto-validators has both go_proto and gogo_proto targets, but the checked # in go files are generated by gogo proto. Resolving to the gogo proto target preserves the behavior of Go modules. From 16a9c0cfbc125847770f1abd8e22ab7486f1a92b Mon Sep 17 00:00:00 2001 From: Ed Schouten Date: Wed, 12 Feb 2025 15:35:55 +0100 Subject: [PATCH 07/27] Stop using Label.workspace_name (#2033) **What type of PR is this?** Other **What package or component does this PR mostly affect?** internal/bzlmod **What does this PR do? Why is it needed?** According to Bazel's documentation it is deprecated: https://bazel.build/rules/lib/builtins/Label#workspace_name We should use Label.repo_name instead. **Which issues(s) does this PR fix?** I am working on an analysis tool that is capable of parsing BUILD/*.bzl files. It currently fails to process these files due to it not supporting Label.workspace_name. Instead of adding support for it to my tool, I think I'll just use the occasion to fix upstream rules. **Other notes for review** --- internal/bzlmod/go_deps.bzl | 4 ++-- internal/bzlmod/go_mod.bzl | 4 ++-- internal/bzlmod/non_module_deps.bzl | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/internal/bzlmod/go_deps.bzl b/internal/bzlmod/go_deps.bzl index 23e33e58d..6e57e84d8 100644 --- a/internal/bzlmod/go_deps.bzl +++ b/internal/bzlmod/go_deps.bzl @@ -425,7 +425,7 @@ def _go_deps_impl(module_ctx): # Collect the relative path of the root module's go.mod file if it lives in the main # repository. - if module.is_root and not from_file_tag.go_mod.workspace_name: + if module.is_root and not from_file_tag.go_mod.repo_name: go_mod = "go.mod" if from_file_tag.go_mod.package: go_mod = from_file_tag.go_mod.package + "/" + go_mod @@ -453,7 +453,7 @@ def _go_deps_impl(module_ctx): if module_path not in bazel_deps or version > bazel_deps[module_path].version: bazel_deps[module_path] = struct( module_name = module.name, - repo_name = "@" + from_file_tag.go_mod.workspace_name, + repo_name = "@" + from_file_tag.go_mod.repo_name, version = version, raw_version = raw_version, ) diff --git a/internal/bzlmod/go_mod.bzl b/internal/bzlmod/go_mod.bzl index 5baed71e9..30c2bd044 100644 --- a/internal/bzlmod/go_mod.bzl +++ b/internal/bzlmod/go_mod.bzl @@ -106,7 +106,7 @@ def parse_go_work(content, go_work_label): major, minor = go.split(".")[:2] - go_mods = [use_spec_to_label(go_work_label.workspace_name, use) for use in state["use"]] + go_mods = [use_spec_to_label(go_work_label.repo_name, use) for use in state["use"]] from_file_tags = [struct(go_mod = go_mod, _is_dev_dependency = False) for go_mod in go_mods] module_tags = [struct(version = mod.version, path = mod.to_path, _parent_label = go_work_label, local_path = mod.local_path, indirect = False) for mod in state["replace"].values()] @@ -432,7 +432,7 @@ def parse_sumfile(module_ctx, label, sumfile): # changes. We have to use a canonical label as we may not have visibility # into the module that provides the sumfile sum_label = Label("@@{}//{}:{}".format( - label.workspace_name, + label.repo_name, label.package, sumfile, )) diff --git a/internal/bzlmod/non_module_deps.bzl b/internal/bzlmod/non_module_deps.bzl index d7572c263..b269a616b 100644 --- a/internal/bzlmod/non_module_deps.bzl +++ b/internal/bzlmod/non_module_deps.bzl @@ -42,8 +42,8 @@ visibility("//") def _non_module_deps_impl(module_ctx): go_repository_cache( name = "bazel_gazelle_go_repository_cache", - # Label.workspace_name is always a canonical name, so use a canonical label. - go_sdk_name = "@" + HOST_COMPATIBLE_SDK.workspace_name, + # Label.repo_name is always a canonical name, so use a canonical label. + go_sdk_name = "@" + HOST_COMPATIBLE_SDK.repo_name, go_env = GO_ENV, ) go_repository_tools( From f1eacd9a3719d91a4912f4192b76b040b844f04b Mon Sep 17 00:00:00 2001 From: Neil Conway Date: Fri, 14 Feb 2025 10:33:31 -0500 Subject: [PATCH 08/27] Update docs for Google API name resolution (#2035) Auto-resolving Google APIs to `@go_googleapis` was removed in 0.32. **What type of PR is this?** > Documentation --- README.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.rst b/README.rst index f7dfe53d3..dc87e0a8d 100644 --- a/README.rst +++ b/README.rst @@ -1171,8 +1171,7 @@ below to resolve dependencies: a) Imports of Well Known Types are mapped to rules in ``@io_bazel_rules_go//proto/wkt``. - b) Imports of Google APIs are mapped to ``@go_googleapis``. - c) Imports of ``github.com/golang/protobuf/ptypes``, ``descriptor``, and + b) Imports of ``github.com/golang/protobuf/ptypes``, ``descriptor``, and ``jsonpb`` are mapped to special rules in ``@com_github_golang_protobuf``. See `Avoiding conflicts with proto rules`_. From 8d5c7030923d4a5d11e0963a8ab8b1e672debf93 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Mon, 17 Feb 2025 20:13:11 +0100 Subject: [PATCH 09/27] Watch inputs to `go_repository` (#2037) **What type of PR is this?** Bug fix **What package or component does this PR mostly affect?** go_repository **What does this PR do? Why is it needed?** Bazel 8 no longer watches `Label`s passed to `repository_ctx.path` by default, which can result in `go_repository`s not being refetched when the Go env or Gazelle changes. **Which issues(s) does this PR fix?** Related to bazel-contrib/rules_go#4278 **Other notes for review** --- internal/go_repository.bzl | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/internal/go_repository.bzl b/internal/go_repository.bzl index 8bcf2561d..bf097b7eb 100644 --- a/internal/go_repository.bzl +++ b/internal/go_repository.bzl @@ -125,14 +125,17 @@ def _go_repository_impl(ctx): is_module_extension_repo = bool(ctx.attr.internal_only_do_not_use_apparent_name) - # Declare Label dependencies at the top of function to avoid unnecessary fetching: - # https://docs.bazel.build/versions/main/skylark/repository_rules.html#when-is-the-implementation-function-executed + # Explicitly watch label dependencies as they are only used as execute arguments. + # https://bazel.build/extending/repo#when_is_the_implementation_function_executed go_env_cache = str(ctx.path(Label("@bazel_gazelle_go_repository_cache//:go.env"))) + watch(ctx, go_env_cache) fetch_repo = str(ctx.path(Label("@bazel_gazelle_go_repository_tools//:bin/fetch_repo{}".format(executable_extension(ctx))))) + watch(ctx, fetch_repo) generate = ctx.attr.build_file_generation in ["on", "clean"] _gazelle = "@bazel_gazelle_go_repository_tools//:bin/gazelle{}".format(executable_extension(ctx)) if generate: gazelle_path = ctx.path(Label(_gazelle)) + watch(ctx, gazelle_path) if ctx.attr.local_path: if hasattr(ctx, "watch_tree"): From b36a3edc391892e53f03e5219b221ed513b209ec Mon Sep 17 00:00:00 2001 From: Jason Bedard Date: Tue, 18 Feb 2025 11:37:46 -0800 Subject: [PATCH 10/27] perf: do not build trie for directories not being traversed (#2036) **What type of PR is this?** > Other **What package or component does this PR mostly affect?** > all **What does this PR do? Why is it needed?** Performance **Which issues(s) does this PR fix?** **Other notes for review** Co-authored-by: Jay Conrod --- walk/walk.go | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/walk/walk.go b/walk/walk.go index 3ff813daf..9bfc5efd5 100644 --- a/walk/walk.go +++ b/walk/walk.go @@ -123,7 +123,7 @@ func Walk(c *config.Config, cexts []config.Configurer, dirs []string, mode Mode, log.Printf("error loading .bazelignore: %v", err) } - trie, err := buildTrie(c, isBazelIgnored) + trie, err := buildTrie(c, updateRels, isBazelIgnored) if err != nil { log.Fatalf("error walking the file system: %v\n", err) } @@ -375,7 +375,7 @@ func newTrie(entry fs.DirEntry) *pathTrie { } } -func buildTrie(c *config.Config, isIgnored isIgnoredFunc) (*pathTrie, error) { +func buildTrie(c *config.Config, updateRels *UpdateFilter, isIgnored isIgnoredFunc) (*pathTrie, error) { trie := &pathTrie{} // A channel to limit the number of concurrent goroutines @@ -384,14 +384,14 @@ func buildTrie(c *config.Config, isIgnored isIgnoredFunc) (*pathTrie, error) { // An error group to handle error propagation eg := errgroup.Group{} eg.Go(func() error { - return walkDir(c.RepoRoot, "", &eg, limitCh, isIgnored, trie) + return walkDir(c.RepoRoot, "", &eg, limitCh, updateRels, isIgnored, trie) }) return trie, eg.Wait() } // walkDir recursively and concurrently descends into the 'rel' directory and builds a trie -func walkDir(root, rel string, eg *errgroup.Group, limitCh chan struct{}, isIgnored isIgnoredFunc, trie *pathTrie) error { +func walkDir(root, rel string, eg *errgroup.Group, limitCh chan struct{}, updateRels *UpdateFilter, isIgnored isIgnoredFunc, trie *pathTrie) error { limitCh <- struct{}{} defer (func() { <-limitCh })() @@ -410,10 +410,15 @@ func walkDir(root, rel string, eg *errgroup.Group, limitCh chan struct{}, isIgno } if entry.IsDir() { + // Ignore directories not even being visited + if !updateRels.shouldVisit(entryPath, true) { + continue + } + entryTrie := newTrie(entry) trie.children = append(trie.children, entryTrie) eg.Go(func() error { - return walkDir(root, entryPath, eg, limitCh, isIgnored, entryTrie) + return walkDir(root, entryPath, eg, limitCh, updateRels, isIgnored, entryTrie) }) } else { trie.files = append(trie.files, entry) From 2b9ae2de8c83bc5812ba3587a03ccddd596301da Mon Sep 17 00:00:00 2001 From: Jason Bedard Date: Tue, 18 Feb 2025 14:55:41 -0800 Subject: [PATCH 11/27] feat: support only updating existing BUILD files (#1921) Close #1832 **What type of PR is this?** Feature > Other **What package or component does this PR mostly affect?** > all **What does this PR do? Why is it needed?** Add an option to enable only updating existing BUILDs and not creating any new ones. I think this is common among extension authors, normally implemented with a quick-exit within `Configure` if the `*rule.File` is `nil`. However all the subdirs then need to be manually traversed to find files in subdirectories with no BUILD. I've always maintained a patch similar to this PR to avoid plugins having to do manual fs operations. **Which issues(s) does this PR fix?** Fixes #1832 **Other notes for review** --- README.rst | 5 ++++ walk/config.go | 31 +++++++++++++++++++--- walk/walk.go | 29 ++++++++++++++++----- walk/walk_test.go | 65 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 119 insertions(+), 11 deletions(-) diff --git a/README.rst b/README.rst index dc87e0a8d..ef2c8ab0f 100644 --- a/README.rst +++ b/README.rst @@ -760,6 +760,11 @@ The following directives are recognized: | your project contains non-Bazel files named ``BUILD`` (or ``build`` on | | case-insensitive file systems). | +---------------------------------------------------+----------------------------------------+ +| :direc:`# gazelle:generation_mode` | ``create_and_update`` | ++---------------------------------------------------+----------------------------------------+ +| Declares if gazelle should create and update BUILD files per directory or only update | +| existing BUILD files. Valid values are: ``create_and_update`` and ``update_only``. | ++---------------------------------------------------+----------------------------------------+ | :direc:`# gazelle:build_tags foo,bar` | none | +---------------------------------------------------+----------------------------------------+ | List of Go build tags Gazelle will defer to Bazel for evaluation. Gazelle applies | diff --git a/walk/config.go b/walk/config.go index 6cee8a371..bae16a1bc 100644 --- a/walk/config.go +++ b/walk/config.go @@ -33,14 +33,27 @@ import ( gzflag "github.com/bazelbuild/bazel-gazelle/flag" ) +// generationModeType represents one of the generation modes. +type generationModeType string + +// Generation modes +const ( + // Update: update and maintain existing BUILD files + generationModeUpdate generationModeType = "update_only" + + // Create: create new and update existing BUILD files + generationModeCreate generationModeType = "create_and_update" +) + // TODO(#472): store location information to validate each exclude. They // may be set in one directory and used in another. Excludes work on // declared generated files, so we can't just stat. type walkConfig struct { - excludes []string - ignore bool - follow []string + updateOnly bool + excludes []string + ignore bool + follow []string } const walkName = "_walk" @@ -70,7 +83,7 @@ func (*Configurer) RegisterFlags(fs *flag.FlagSet, cmd string, c *config.Config) func (*Configurer) CheckFlags(fs *flag.FlagSet, c *config.Config) error { return nil } func (*Configurer) KnownDirectives() []string { - return []string{"exclude", "follow", "ignore"} + return []string{"generation_mode", "exclude", "follow", "ignore"} } func (cr *Configurer) Configure(c *config.Config, rel string, f *rule.File) { @@ -82,6 +95,16 @@ func (cr *Configurer) Configure(c *config.Config, rel string, f *rule.File) { if f != nil { for _, d := range f.Directives { switch d.Key { + case "generation_mode": + switch generationModeType(strings.TrimSpace(d.Value)) { + case generationModeUpdate: + wcCopy.updateOnly = true + case generationModeCreate: + wcCopy.updateOnly = false + default: + log.Fatalf("unknown generation_mode %q in //%s", d.Value, f.Pkg) + continue + } case "exclude": if err := checkPathMatchPattern(path.Join(rel, d.Value)); err != nil { log.Printf("the exclusion pattern is not valid %q: %s", path.Join(rel, d.Value), err) diff --git a/walk/walk.go b/walk/walk.go index 9bfc5efd5..32d0b4b96 100644 --- a/walk/walk.go +++ b/walk/walk.go @@ -140,7 +140,7 @@ func Walk(c *config.Config, cexts []config.Configurer, dirs []string, mode Mode, // // Traversal may skip subtrees or files based on the config.Config exclude/ignore/follow options // as well as the UpdateFilter callbacks. -func visit(c *config.Config, cexts []config.Configurer, knownDirectives map[string]bool, updateRels *UpdateFilter, trie *pathTrie, wf WalkFunc, rel string, updateParent bool) { +func visit(c *config.Config, cexts []config.Configurer, knownDirectives map[string]bool, updateRels *UpdateFilter, trie *pathTrie, wf WalkFunc, rel string, updateParent bool) ([]string, bool) { haveError := false // Absolute path to the directory being visited @@ -157,11 +157,16 @@ func visit(c *config.Config, cexts []config.Configurer, knownDirectives map[stri haveError = true } - configure(cexts, knownDirectives, c, rel, f) - wc := getWalkConfig(c) + collectionOnly := f == nil && getWalkConfig(c).updateOnly + + // Configure the current directory if not only collecting files + if !collectionOnly { + configure(cexts, knownDirectives, c, rel, f) + } + wc := getWalkConfig(c) if wc.isExcluded(rel) { - return + return nil, false } // Filter and collect files @@ -188,19 +193,29 @@ func visit(c *config.Config, cexts []config.Configurer, knownDirectives map[stri continue } if ent := resolveFileInfo(wc, dir, entRel, t.entry); ent != nil { - subdirs = append(subdirs, base) - if updateRels.shouldVisit(entRel, shouldUpdate) { - visit(c.Clone(), cexts, knownDirectives, updateRels, t, wf, entRel, shouldUpdate) + subFiles, shouldMerge := visit(c.Clone(), cexts, knownDirectives, updateRels, t, wf, entRel, shouldUpdate) + if shouldMerge { + for _, f := range subFiles { + regularFiles = append(regularFiles, path.Join(base, f)) + } + } else { + subdirs = append(subdirs, base) + } } } } + if collectionOnly { + return regularFiles, true + } + update := !haveError && !wc.ignore && shouldUpdate if updateRels.shouldCall(rel, updateParent) { genFiles := findGenFiles(wc, f) wf(dir, rel, c, update, f, subdirs, regularFiles, genFiles) } + return nil, false } // An UpdateFilter tracks which directories need to be updated diff --git a/walk/walk_test.go b/walk/walk_test.go index 7c3906c20..d29b22307 100644 --- a/walk/walk_test.go +++ b/walk/walk_test.go @@ -17,8 +17,10 @@ package walk import ( "flag" + "fmt" "path" "path/filepath" + "reflect" "testing" "github.com/bazelbuild/bazel-gazelle/config" @@ -141,6 +143,69 @@ func TestUpdateDirs(t *testing.T) { } } +func TestGenMode(t *testing.T) { + dir, cleanup := testtools.CreateFiles(t, []testtools.FileSpec{ + {Path: "mode-create/"}, + {Path: "mode-create/a.go"}, + {Path: "mode-create/sub/"}, + {Path: "mode-create/sub/b.go"}, + {Path: "mode-create/sub/sub2/"}, + {Path: "mode-create/sub/sub2/sub3/c.go"}, + {Path: "mode-update/"}, + { + Path: "mode-update/BUILD.bazel", + Content: "# gazelle:generation_mode update_only", + }, + {Path: "mode-update/a.go"}, + {Path: "mode-update/sub/"}, + {Path: "mode-update/sub/b.go"}, + {Path: "mode-update/sub/sub2/"}, + {Path: "mode-update/sub/sub2/sub3/c.go"}, + {Path: "mode-update/sub/sub3/"}, + {Path: "mode-update/sub/sub3/BUILD.bazel"}, + {Path: "mode-update/sub/sub3/d.go"}, + {Path: "mode-update/sub/sub3/sub4/"}, + {Path: "mode-update/sub/sub3/sub4/e.go"}, + }) + defer cleanup() + + type visitSpec struct { + subdirs, files []string + } + + t.Run("generation_mode create vs update", func(t *testing.T) { + c, cexts := testConfig(t, dir) + var visits []visitSpec + Walk(c, cexts, []string{"."}, VisitAllUpdateSubdirsMode, func(_ string, rel string, _ *config.Config, update bool, _ *rule.File, subdirs, regularFiles, _ []string) { + visits = append(visits, visitSpec{ + subdirs: subdirs, + files: regularFiles, + }) + }) + + if len(visits) != 7 { + t.Error(fmt.Sprintf("Expected 7 visits, got %v", len(visits))) + } + + if !reflect.DeepEqual(visits[len(visits)-1].subdirs, []string{"mode-create", "mode-update"}) { + t.Errorf("Last visit should be root dir with 2 subdirs") + } + + if len(visits[0].subdirs) != 0 || len(visits[0].files) != 1 || visits[0].files[0] != "c.go" { + t.Errorf("Leaf visit should be only files: %v", visits[0]) + } + modeUpdateFiles1 := []string{"BUILD.bazel", "d.go", "sub4/e.go"} + if !reflect.DeepEqual(visits[4].files, modeUpdateFiles1) { + t.Errorf("update mode should contain files in subdirs. Want %v, got: %v", modeUpdateFiles1, visits[5].files) + } + + modeUpdateFiles2 := []string{"BUILD.bazel", "a.go", "sub/b.go", "sub/sub2/sub3/c.go"} + if !reflect.DeepEqual(visits[5].files, modeUpdateFiles2) { + t.Errorf("update mode should contain files in subdirs. Want %v, got: %v", modeUpdateFiles2, visits[5].files) + } + }) +} + func TestCustomBuildName(t *testing.T) { dir, cleanup := testtools.CreateFiles(t, []testtools.FileSpec{ { From 798a462f714ac8b004603dc0b2a80ae65235127f Mon Sep 17 00:00:00 2001 From: Jason Bedard Date: Fri, 21 Feb 2025 17:38:30 -0800 Subject: [PATCH 12/27] feat: add support for bazel8 REPO.bazel ignore_directories (#2039) **What type of PR is this?** Feature **What package or component does this PR mostly affect?** all **What does this PR do? Why is it needed?** Support the bazel8 [ignore_directories](https://github.com/bazel-contrib/bazel-gazelle/issues/1994) **Which issues(s) does this PR fix?** Fixes #1994 **Other notes for review** This is handled separately from `.bazelignore` because a) it only excludes directories and does not have to be checked on every file and b) it uses globs and is therefor a lot slower then `.bazelignore` --- tests/bazelignore/BUILD.out | 1 + tests/bazelignore/REPO.bazel | 14 ++++++ tests/bazelignore/repo1/file2 | 1 + tests/bazelignore/repo2/file2 | 1 + tests/bazelignore/repo3/sub2/file1 | 1 + tests/bazelignore/sub2/BUILD.out | 5 +- tests/bazelignore/sub2/repo1/BUILD.out | 9 ++++ tests/bazelignore/sub2/repo1/file4 | 1 + tests/bazelignore/sub2/repo1/repo2 | 1 + tests/bazelignore/sub2/repo2/file2 | 1 + tests/bazelignore/sub2/repo4-a/file4 | 1 + tests/bazelignore/sub2/repo4-b/file4 | 1 + walk/BUILD.bazel | 1 + walk/config.go | 68 +++++++++++++++++++++++++- walk/walk.go | 21 +++++--- 15 files changed, 118 insertions(+), 9 deletions(-) create mode 100644 tests/bazelignore/REPO.bazel create mode 100644 tests/bazelignore/repo1/file2 create mode 100644 tests/bazelignore/repo2/file2 create mode 100644 tests/bazelignore/repo3/sub2/file1 create mode 100644 tests/bazelignore/sub2/repo1/BUILD.out create mode 100644 tests/bazelignore/sub2/repo1/file4 create mode 100644 tests/bazelignore/sub2/repo1/repo2 create mode 100644 tests/bazelignore/sub2/repo2/file2 create mode 100644 tests/bazelignore/sub2/repo4-a/file4 create mode 100644 tests/bazelignore/sub2/repo4-b/file4 diff --git a/tests/bazelignore/BUILD.out b/tests/bazelignore/BUILD.out index 4099b4981..f0e56862a 100644 --- a/tests/bazelignore/BUILD.out +++ b/tests/bazelignore/BUILD.out @@ -4,6 +4,7 @@ filegroup( srcs = [ ".bazelignore", "MODULE.bazel", + "REPO.bazel", "//sub2:all_files", ], visibility = ["//visibility:public"], diff --git a/tests/bazelignore/REPO.bazel b/tests/bazelignore/REPO.bazel new file mode 100644 index 000000000..d530fcb2b --- /dev/null +++ b/tests/bazelignore/REPO.bazel @@ -0,0 +1,14 @@ +repo() + +# A single ignore_directories() with various comments and syntaxes +ignore_directories([ + "repo1", + # single line comment within array + "**/repo2", + "**/repo3/**", # trailing comment within array and doublestars + "sub*/repo4-*", +]) + +# Only a single ignore_directories() is supported so add a few more to ensure they are ignored +ignore_directories(["*", "**", "**/repo1"]) +ignore_directories("very", "invalid", 42) diff --git a/tests/bazelignore/repo1/file2 b/tests/bazelignore/repo1/file2 new file mode 100644 index 000000000..80a6b80f8 --- /dev/null +++ b/tests/bazelignore/repo1/file2 @@ -0,0 +1 @@ +this is excluded because it is 'repo1' in the root diff --git a/tests/bazelignore/repo2/file2 b/tests/bazelignore/repo2/file2 new file mode 100644 index 000000000..d71a43b3d --- /dev/null +++ b/tests/bazelignore/repo2/file2 @@ -0,0 +1 @@ +this is excluded because it is a 'repo2' anywhere in the repo diff --git a/tests/bazelignore/repo3/sub2/file1 b/tests/bazelignore/repo3/sub2/file1 new file mode 100644 index 000000000..59a65acac --- /dev/null +++ b/tests/bazelignore/repo3/sub2/file1 @@ -0,0 +1 @@ +excluded because anything within **/repo3/** is excluded diff --git a/tests/bazelignore/sub2/BUILD.out b/tests/bazelignore/sub2/BUILD.out index aa8a95f07..98c3dcab8 100644 --- a/tests/bazelignore/sub2/BUILD.out +++ b/tests/bazelignore/sub2/BUILD.out @@ -1,6 +1,9 @@ filegroup( name = "all_files", testonly = True, - srcs = ["file3"], + srcs = [ + "file3", + "//sub2/repo1:all_files", + ], visibility = ["//visibility:public"], ) diff --git a/tests/bazelignore/sub2/repo1/BUILD.out b/tests/bazelignore/sub2/repo1/BUILD.out new file mode 100644 index 000000000..1ff9438e1 --- /dev/null +++ b/tests/bazelignore/sub2/repo1/BUILD.out @@ -0,0 +1,9 @@ +filegroup( + name = "all_files", + testonly = True, + srcs = [ + "file4", + "repo2", + ], + visibility = ["//visibility:public"], +) diff --git a/tests/bazelignore/sub2/repo1/file4 b/tests/bazelignore/sub2/repo1/file4 new file mode 100644 index 000000000..d974616ad --- /dev/null +++ b/tests/bazelignore/sub2/repo1/file4 @@ -0,0 +1 @@ +this is included because it is not 'repo1' in the root diff --git a/tests/bazelignore/sub2/repo1/repo2 b/tests/bazelignore/sub2/repo1/repo2 new file mode 100644 index 000000000..7a325771a --- /dev/null +++ b/tests/bazelignore/sub2/repo1/repo2 @@ -0,0 +1 @@ +this is not excluded because it is not a directory diff --git a/tests/bazelignore/sub2/repo2/file2 b/tests/bazelignore/sub2/repo2/file2 new file mode 100644 index 000000000..d71a43b3d --- /dev/null +++ b/tests/bazelignore/sub2/repo2/file2 @@ -0,0 +1 @@ +this is excluded because it is a 'repo2' anywhere in the repo diff --git a/tests/bazelignore/sub2/repo4-a/file4 b/tests/bazelignore/sub2/repo4-a/file4 new file mode 100644 index 000000000..d03d9953a --- /dev/null +++ b/tests/bazelignore/sub2/repo4-a/file4 @@ -0,0 +1 @@ +excluded due to **/repo* diff --git a/tests/bazelignore/sub2/repo4-b/file4 b/tests/bazelignore/sub2/repo4-b/file4 new file mode 100644 index 000000000..d03d9953a --- /dev/null +++ b/tests/bazelignore/sub2/repo4-b/file4 @@ -0,0 +1 @@ +excluded due to **/repo* diff --git a/walk/BUILD.bazel b/walk/BUILD.bazel index a2b5272c5..7daedb748 100644 --- a/walk/BUILD.bazel +++ b/walk/BUILD.bazel @@ -12,6 +12,7 @@ go_library( "//config", "//flag", "//rule", + "@com_github_bazelbuild_buildtools//build", "@com_github_bmatcuk_doublestar_v4//:doublestar", "@org_golang_x_sync//errgroup", ], diff --git a/walk/config.go b/walk/config.go index bae16a1bc..8c59f967c 100644 --- a/walk/config.go +++ b/walk/config.go @@ -28,6 +28,7 @@ import ( "github.com/bazelbuild/bazel-gazelle/config" "github.com/bazelbuild/bazel-gazelle/rule" + bzl "github.com/bazelbuild/buildtools/build" "github.com/bmatcuk/doublestar/v4" gzflag "github.com/bazelbuild/bazel-gazelle/flag" @@ -166,12 +167,75 @@ func loadBazelIgnore(repoRoot string) (isIgnoredFunc, error) { excludes[ignore] = struct{}{} } - isIgnored := func(p string) bool { + isBazelIgnored := func(p string) bool { _, ok := excludes[p] return ok } - return isIgnored, nil + return isBazelIgnored, nil +} + +func loadRepoDirectoryIgnore(repoRoot string) (isIgnoredFunc, error) { + repoFilePath := path.Join(repoRoot, "REPO.bazel") + repoFileContent, err := os.ReadFile(repoFilePath) + if errors.Is(err, fs.ErrNotExist) { + return nothingIgnored, nil + } + if err != nil { + return nothingIgnored, fmt.Errorf("REPO.bazel exists but couldn't be read: %v", err) + } + + ast, err := bzl.Parse(repoRoot, repoFileContent) + if err != nil { + return nothingIgnored, fmt.Errorf("failed to parse REPO.bazel: %v", err) + } + + var ignoreDirectories []string + + // Search for ignore_directories([...ignore strings...]) + for _, expr := range ast.Stmt { + if call, isCall := expr.(*bzl.CallExpr); isCall { + if inv, isIdentCall := call.X.(*bzl.Ident); isIdentCall && inv.Name == "ignore_directories" { + if len(call.List) != 1 { + return nothingIgnored, fmt.Errorf("REPO.bazel ignore_directories() expects one argument") + } + + list, isList := call.List[0].(*bzl.ListExpr) + if !isList { + return nothingIgnored, fmt.Errorf("REPO.bazel ignore_directories() unexpected argument type: %T", call.List[0]) + } + + for _, item := range list.List { + if strExpr, isStr := item.(*bzl.StringExpr); isStr { + if err := checkPathMatchPattern(strExpr.Value); err != nil { + log.Printf("the ignore_directories() pattern %q is not valid: %s", strExpr.Value, err) + continue + } + + ignoreDirectories = append(ignoreDirectories, strExpr.Value) + } + } + + // Only a single ignore_directories() is supported in REPO.bazel and searching can stop. + break + } + } + } + + if len(ignoreDirectories) == 0 { + return nothingIgnored, nil + } + + isRepoIgnored := func(p string) bool { + for _, ignore := range ignoreDirectories { + if doublestar.MatchUnvalidated(ignore, p) { + return true + } + } + return false + } + + return isRepoIgnored, nil } func checkPathMatchPattern(pattern string) error { diff --git a/walk/walk.go b/walk/walk.go index 32d0b4b96..612a7d579 100644 --- a/walk/walk.go +++ b/walk/walk.go @@ -123,7 +123,12 @@ func Walk(c *config.Config, cexts []config.Configurer, dirs []string, mode Mode, log.Printf("error loading .bazelignore: %v", err) } - trie, err := buildTrie(c, updateRels, isBazelIgnored) + isRepoDirectoryIgnored, err := loadRepoDirectoryIgnore(c.RepoRoot) + if err != nil { + log.Printf("error loading REPO.bazel ignore_directories(): %v", err) + } + + trie, err := buildTrie(c, updateRels, isBazelIgnored, isRepoDirectoryIgnored) if err != nil { log.Fatalf("error walking the file system: %v\n", err) } @@ -390,7 +395,7 @@ func newTrie(entry fs.DirEntry) *pathTrie { } } -func buildTrie(c *config.Config, updateRels *UpdateFilter, isIgnored isIgnoredFunc) (*pathTrie, error) { +func buildTrie(c *config.Config, updateRels *UpdateFilter, isBazelIgnored, isRepoDirectoryIgnored isIgnoredFunc) (*pathTrie, error) { trie := &pathTrie{} // A channel to limit the number of concurrent goroutines @@ -399,14 +404,14 @@ func buildTrie(c *config.Config, updateRels *UpdateFilter, isIgnored isIgnoredFu // An error group to handle error propagation eg := errgroup.Group{} eg.Go(func() error { - return walkDir(c.RepoRoot, "", &eg, limitCh, updateRels, isIgnored, trie) + return walkDir(c.RepoRoot, "", &eg, limitCh, updateRels, isBazelIgnored, isRepoDirectoryIgnored, trie) }) return trie, eg.Wait() } // walkDir recursively and concurrently descends into the 'rel' directory and builds a trie -func walkDir(root, rel string, eg *errgroup.Group, limitCh chan struct{}, updateRels *UpdateFilter, isIgnored isIgnoredFunc, trie *pathTrie) error { +func walkDir(root, rel string, eg *errgroup.Group, limitCh chan struct{}, updateRels *UpdateFilter, isBazelIgnored, isRepoDirectoryIgnored isIgnoredFunc, trie *pathTrie) error { limitCh <- struct{}{} defer (func() { <-limitCh })() @@ -420,11 +425,15 @@ func walkDir(root, rel string, eg *errgroup.Group, limitCh chan struct{}, update entryPath := path.Join(rel, entryName) // Ignore .git, empty names and ignored paths - if entryName == "" || entryName == ".git" || isIgnored(entryPath) { + if entryName == "" || entryName == ".git" || isBazelIgnored(entryPath) { continue } if entry.IsDir() { + if isRepoDirectoryIgnored(entryPath) { + continue + } + // Ignore directories not even being visited if !updateRels.shouldVisit(entryPath, true) { continue @@ -433,7 +442,7 @@ func walkDir(root, rel string, eg *errgroup.Group, limitCh chan struct{}, update entryTrie := newTrie(entry) trie.children = append(trie.children, entryTrie) eg.Go(func() error { - return walkDir(root, entryPath, eg, limitCh, updateRels, isIgnored, entryTrie) + return walkDir(root, entryPath, eg, limitCh, updateRels, isBazelIgnored, isRepoDirectoryIgnored, entryTrie) }) } else { trie.files = append(trie.files, entry) From 59756d76e1446ae62ee8b5129fb968d983a4b393 Mon Sep 17 00:00:00 2001 From: Gustavo Goretkin Date: Sun, 23 Feb 2025 15:03:54 -0500 Subject: [PATCH 13/27] Fix a typo in the target name, and use relative target path (#2024) **What type of PR is this?** > Uncomment one line below and remove others. > > Bug fix > Feature > Documentation > Other **What package or component does this PR mostly affect?** > For example: > > language/go > cmd/gazelle > go_repository > all **What does this PR do? Why is it needed?** **Which issues(s) does this PR fix?** Fixes # **Other notes for review** --------- Co-authored-by: Gustavo Nunes Goretkin --- extend.md | 4 ++-- internal/extend_docs.bzl | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/extend.md b/extend.md index b7c272b9c..a225983d1 100644 --- a/extend.md +++ b/extend.md @@ -50,7 +50,7 @@ libraries through the `DEFAULT_LANGUAGES` list (you may want to use load("@bazel_gazelle//:def.bzl", "DEFAULT_LANGUAGES", "gazelle_binary") gazelle_binary( - name = "gazelle", + name = "my_gazelle_binary", languages = [ "@rules_python//gazelle", # Use gazelle from rules_python. "@bazel_gazelle//language/go", # Built-in rule from gazelle for Golang. @@ -70,7 +70,7 @@ load("@bazel_gazelle//:def.bzl", "gazelle") # gazelle:prefix example.com/project gazelle( name = "gazelle", - gazelle = "//:my_gazelle_binary", + gazelle = ":my_gazelle_binary", ) ``` diff --git a/internal/extend_docs.bzl b/internal/extend_docs.bzl index 3ea4a8640..154ec8a4f 100644 --- a/internal/extend_docs.bzl +++ b/internal/extend_docs.bzl @@ -51,7 +51,7 @@ libraries through the `DEFAULT_LANGUAGES` list (you may want to use load("@bazel_gazelle//:def.bzl", "DEFAULT_LANGUAGES", "gazelle_binary") gazelle_binary( - name = "gazelle", + name = "my_gazelle_binary", languages = [ "@rules_python//gazelle", # Use gazelle from rules_python. "@bazel_gazelle//language/go", # Built-in rule from gazelle for Golang. @@ -71,7 +71,7 @@ load("@bazel_gazelle//:def.bzl", "gazelle") # gazelle:prefix example.com/project gazelle( name = "gazelle", - gazelle = "//:my_gazelle_binary", + gazelle = ":my_gazelle_binary", ) ``` From 00e43d579854fa8d2fdebb30c588e00a0386ec1e Mon Sep 17 00:00:00 2001 From: Duan Yihan <69476139+IanDxSSXX@users.noreply.github.com> Date: Mon, 24 Feb 2025 00:32:31 -0800 Subject: [PATCH 14/27] fix: attr.val is not updated when setting multiple times (#2046) **What type of PR is this?** Bug fix **What package or component does this PR mostly affect?** rule **Which issues(s) does this PR fix?** The attr.val field was introduced in the commit a2a604d as part of the R`ule.SetAttr` implementation. However, there is a bug where updating `attr.val` does not correctly propagate the change to `r.attrs[key].val`. The issue stems from Go's map value semantics. When a value is retrieved from a map, it is returned as a copy, not a reference. In the current implementation: ```go if attr, ok := r.attrs[key]; ok { attr.expr.RHS = rhs // Fine because RHS is a reference attr.val = value // Modifies the copy, not the original value in the map } ``` As a result, changes to attr.val are not reflected in r.attrs[key].val. **Related issue** https://github.com/bazel-contrib/bazel-gazelle/issues/2045 --- rule/rule.go | 1 + 1 file changed, 1 insertion(+) diff --git a/rule/rule.go b/rule/rule.go index b15f87f6b..f690978c3 100644 --- a/rule/rule.go +++ b/rule/rule.go @@ -960,6 +960,7 @@ func (r *Rule) SetAttr(key string, value interface{}) { if attr, ok := r.attrs[key]; ok { attr.expr.RHS = rhs attr.val = value + r.attrs[key] = attr } else { r.attrs[key] = attrValue{ expr: &bzl.AssignExpr{ From 2f61d7878976df8cf08c81fa2c0613a96987f3c3 Mon Sep 17 00:00:00 2001 From: Jay Conrod Date: Mon, 24 Feb 2025 10:36:48 -0800 Subject: [PATCH 15/27] walk: add benchmark for Walk (#2042) Somewhat related to #1891, since we've been talking about performance **What type of PR is this?** > Other **What package or component does this PR mostly affect?** > walk **What does this PR do? Why is it needed?** Adds a benchmark for the `Walk` function, since we've been talking speculatively about performance. Also changes trie-building parallelism from 100 to 6. Lower parallelism is worse, higher is about the same. **Which issues(s) does this PR fix?** Somewhat related to #1891 **Other notes for review** Run with: ``` bazel test //walk:walk_test --test_arg=-test.bench=. --test_output=streamed --test_filter=none --cache_test_results=false ``` --------- Co-authored-by: Fabian Meumertzheim --- walk/walk.go | 5 +++- walk/walk_test.go | 75 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 1 deletion(-) diff --git a/walk/walk.go b/walk/walk.go index 612a7d579..609e5e21d 100644 --- a/walk/walk.go +++ b/walk/walk.go @@ -399,7 +399,10 @@ func buildTrie(c *config.Config, updateRels *UpdateFilter, isBazelIgnored, isRep trie := &pathTrie{} // A channel to limit the number of concurrent goroutines - limitCh := make(chan struct{}, 100) + // Value chosen by running BenchmarkWalk on a MacBook Pro M1. + // In most cases, walking a tree is memory or I/O bound, not CPU bound, + // so this should be lower than the number of cores. + limitCh := make(chan struct{}, 6) // An error group to handle error propagation eg := errgroup.Group{} diff --git a/walk/walk_test.go b/walk/walk_test.go index d29b22307..9c2833b70 100644 --- a/walk/walk_test.go +++ b/walk/walk_test.go @@ -16,8 +16,10 @@ limitations under the License. package walk import ( + "bytes" "flag" "fmt" + "os" "path" "path/filepath" "reflect" @@ -409,3 +411,76 @@ func (*testConfigurer) KnownDirectives() []string { return nil } func (tc *testConfigurer) Configure(c *config.Config, rel string, f *rule.File) { tc.configure(c, rel, f) } + +// BenchmarkWalk measures how long it takes Walk to traverse a synthetic repo. +// +// There are 10 top-level directories. Each has 10 subdirectories. Each of +// those has 10 subdirectories (so 1001 directories in total). +// +// Each directory has 10 files and a BUILD file with a filegroup that includes +// those files (the content isn't really important, we just want to exercise +// the parser a little bit.) +// +// This is somewhat unrealistic: the whole tree is likely to be in the kernel's +// memory in the kernel's file cache, so this doesn't measure I/O to disk. +// Still, this is frequently true for real projects where Gazelle is invoked. +func BenchmarkWalk(b *testing.B) { + // Create a fake repo to walk. + subdirCount := 10 + fileCount := 10 + levelCount := 3 + + buildFileBuilder := &bytes.Buffer{} + fmt.Fprintf(buildFileBuilder, "filegroup(\n srcs = [\n") + for i := range fileCount { + fmt.Fprintf(buildFileBuilder, " \"f%d\",\n", i) + } + fmt.Fprintf(buildFileBuilder, " ],\n)\n") + buildFileContent := buildFileBuilder.Bytes() + + rootDir := b.TempDir() + var createDir func(string, int) + createDir = func(dir string, level int) { + buildFilePath := filepath.Join(dir, "BUILD") + if err := os.WriteFile(buildFilePath, buildFileContent, 0666); err != nil { + b.Fatal(err) + } + + for i := range fileCount { + filePath := filepath.Join(dir, fmt.Sprintf("f%d", i)) + if err := os.WriteFile(filePath, nil, 0666); err != nil { + b.Fatal(err) + } + } + + if level < levelCount { + for i := range subdirCount { + subdir := filepath.Join(dir, fmt.Sprintf("d%d", i)) + if err := os.Mkdir(subdir, 0777); err != nil { + b.Fatal(err) + } + createDir(subdir, level+1) + } + } + } + createDir(rootDir, 0) + + cexts := []config.Configurer{&Configurer{}} + c := config.New() + c.RepoRoot = rootDir + c.RepoRoot = rootDir + c.IndexLibraries = true + fs := flag.NewFlagSet("gazelle", flag.ContinueOnError) + for _, cext := range cexts { + cext.RegisterFlags(fs, "update", c) + } + + // Benchmark calling Walk with a trivial callback function. + wf := func(dir, rel string, c *config.Config, update bool, f *rule.File, subdirs, regularFiles, genFiles []string) { + } + + b.ResetTimer() + for range b.N { + Walk(c, nil, []string{rootDir}, VisitAllUpdateSubdirsMode, wf) + } +} From 1c82f140631bbb9f5db60dfdc7f7e23006473535 Mon Sep 17 00:00:00 2001 From: Jay Conrod Date: Tue, 25 Feb 2025 07:57:06 -0800 Subject: [PATCH 16/27] walk: set parallelism to runtime.GOMAXPROCS (#2047) **What type of PR is this?** > Other **What package or component does this PR mostly affect?** > walk **What does this PR do? Why is it needed?** Changes parallelism from 6 to GOMAXPROCS. It doesn't seem to hurt and may benefit large CI machines. Discussed in #2042. **Which issues(s) does this PR fix?** Follow-up to #2042 Somewhat related to #1891 **Other notes for review** --------- Co-authored-by: Jason Bedard --- walk/walk.go | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/walk/walk.go b/walk/walk.go index 609e5e21d..697d17346 100644 --- a/walk/walk.go +++ b/walk/walk.go @@ -23,6 +23,7 @@ import ( "os" "path" "path/filepath" + "runtime" "strings" "github.com/bazelbuild/bazel-gazelle/config" @@ -399,10 +400,14 @@ func buildTrie(c *config.Config, updateRels *UpdateFilter, isBazelIgnored, isRep trie := &pathTrie{} // A channel to limit the number of concurrent goroutines - // Value chosen by running BenchmarkWalk on a MacBook Pro M1. - // In most cases, walking a tree is memory or I/O bound, not CPU bound, - // so this should be lower than the number of cores. - limitCh := make(chan struct{}, 6) + // + // This operation is likely to be limited by memory bandwidth and I/O, + // not CPU. On a MacBook Pro M1, 6 was the lowest value with best performance, + // but higher values didn't degrade performance. Higher values may benefit + // machines with more memory bandwidth. + // + // Use BenchmarkWalk to test changes here. + limitCh := make(chan struct{}, runtime.GOMAXPROCS(0)) // An error group to handle error propagation eg := errgroup.Group{} From a8e48fd344cee0adbff2cc846da972c6d941e397 Mon Sep 17 00:00:00 2001 From: Jason Bedard Date: Tue, 25 Feb 2025 12:23:15 -0800 Subject: [PATCH 17/27] refactor: move fs/walk related configuration to walk/config (#2044) With the ongoing changes to fs-walking I think anything related to BUILD file location/generation should be part of the walk configuration. I think this will be required or at least simplify the changes required for [parsing BUILD files during walk](https://github.com/bazel-contrib/bazel-gazelle/pull/1928) and [moving exclusion to walk](https://github.com/bazel-contrib/bazel-gazelle/pull/1928/files#r1962414111). Both of those require knowing the `ValidBuildFileNames` and `ReadBuildFilesDir` during the fs walk. **What type of PR is this?** Refactor **What package or component does this PR mostly affect?** all --- config/config.go | 31 ++++-------------------- config/config_test.go | 14 ++--------- walk/config.go | 47 ++++++++++++++++++++++++++++++------ walk/config_test.go | 56 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 103 insertions(+), 45 deletions(-) diff --git a/config/config.go b/config/config.go index 5aae20c0e..138269221 100644 --- a/config/config.go +++ b/config/config.go @@ -206,19 +206,16 @@ var _ Configurer = (*CommonConfigurer)(nil) // CommonConfigurer handles language-agnostic command-line flags and directives, // i.e., those that apply to Config itself and not to Config.Exts. type CommonConfigurer struct { - repoRoot, buildFileNames, readBuildFilesDir, writeBuildFilesDir string - indexLibraries, strict bool - langCsv string - bzlmod bool + repoRoot string + indexLibraries, strict bool + langCsv string + bzlmod bool } func (cc *CommonConfigurer) RegisterFlags(fs *flag.FlagSet, cmd string, c *Config) { fs.StringVar(&cc.repoRoot, "repo_root", "", "path to a directory which corresponds to go_prefix, otherwise gazelle searches for it.") - fs.StringVar(&cc.buildFileNames, "build_file_name", strings.Join(DefaultValidBuildFileNames, ","), "comma-separated list of valid build file names.\nThe first element of the list is the name of output build files to generate.") fs.BoolVar(&cc.indexLibraries, "index", true, "when true, gazelle will build an index of libraries in the workspace for dependency resolution") fs.BoolVar(&cc.strict, "strict", false, "when true, gazelle will exit with none-zero value for build file syntax errors or unknown directives") - fs.StringVar(&cc.readBuildFilesDir, "experimental_read_build_files_dir", "", "path to a directory where build files should be read from (instead of -repo_root)") - fs.StringVar(&cc.writeBuildFilesDir, "experimental_write_build_files_dir", "", "path to a directory where build files should be written to (instead of -repo_root)") fs.StringVar(&cc.langCsv, "lang", "", "if non-empty, process only these languages (e.g. \"go,proto\")") fs.BoolVar(&cc.bzlmod, "bzlmod", false, "for internal usage only") } @@ -243,21 +240,6 @@ func (cc *CommonConfigurer) CheckFlags(fs *flag.FlagSet, c *Config) error { if err != nil { return fmt.Errorf("%s: failed to resolve symlinks: %v", cc.repoRoot, err) } - c.ValidBuildFileNames = strings.Split(cc.buildFileNames, ",") - if cc.readBuildFilesDir != "" { - if filepath.IsAbs(cc.readBuildFilesDir) { - c.ReadBuildFilesDir = cc.readBuildFilesDir - } else { - c.ReadBuildFilesDir = filepath.Join(c.WorkDir, cc.readBuildFilesDir) - } - } - if cc.writeBuildFilesDir != "" { - if filepath.IsAbs(cc.writeBuildFilesDir) { - c.WriteBuildFilesDir = cc.writeBuildFilesDir - } else { - c.WriteBuildFilesDir = filepath.Join(c.WorkDir, cc.writeBuildFilesDir) - } - } c.IndexLibraries = cc.indexLibraries c.Strict = cc.strict if len(cc.langCsv) > 0 { @@ -272,7 +254,7 @@ func (cc *CommonConfigurer) CheckFlags(fs *flag.FlagSet, c *Config) error { } func (cc *CommonConfigurer) KnownDirectives() []string { - return []string{"build_file_name", "map_kind", "alias_kind", "lang"} + return []string{"map_kind", "alias_kind", "lang"} } func (cc *CommonConfigurer) Configure(c *Config, rel string, f *rule.File) { @@ -281,9 +263,6 @@ func (cc *CommonConfigurer) Configure(c *Config, rel string, f *rule.File) { } for _, d := range f.Directives { switch d.Key { - case "build_file_name": - c.ValidBuildFileNames = strings.Split(d.Value, ",") - case "map_kind": vals := strings.Fields(d.Value) if len(vals) != 3 { diff --git a/config/config_test.go b/config/config_test.go index 0661d047a..e2d5613bd 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -43,7 +43,7 @@ func TestCommonConfigurerFlags(t *testing.T) { cc := &CommonConfigurer{} fs := flag.NewFlagSet("test", flag.ContinueOnError) cc.RegisterFlags(fs, "test", c) - args := []string{"-repo_root", dir, "-build_file_name", "x,y", "-lang", "go"} + args := []string{"-repo_root", dir, "-lang", "go"} if err := fs.Parse(args); err != nil { t.Fatal(err) } @@ -55,11 +55,6 @@ func TestCommonConfigurerFlags(t *testing.T) { t.Errorf("for RepoRoot, got %#v, want %#v", c.RepoRoot, dir) } - wantBuildFileNames := []string{"x", "y"} - if !reflect.DeepEqual(c.ValidBuildFileNames, wantBuildFileNames) { - t.Errorf("for ValidBuildFileNames, got %#v, want %#v", c.ValidBuildFileNames, wantBuildFileNames) - } - wantLangs := []string{"go"} if !reflect.DeepEqual(c.Langs, wantLangs) { t.Errorf("for Langs, got %#v, want %#v", c.Langs, wantLangs) @@ -69,17 +64,12 @@ func TestCommonConfigurerFlags(t *testing.T) { func TestCommonConfigurerDirectives(t *testing.T) { c := New() cc := &CommonConfigurer{} - buildData := []byte(`# gazelle:build_file_name x,y -# gazelle:lang go`) + buildData := []byte(`# gazelle:lang go`) f, err := rule.LoadData(filepath.Join("test", "BUILD.bazel"), "", buildData) if err != nil { t.Fatal(err) } cc.Configure(c, "", f) - want := []string{"x", "y"} - if !reflect.DeepEqual(c.ValidBuildFileNames, want) { - t.Errorf("for ValidBuildFileNames, got %#v, want %#v", c.ValidBuildFileNames, want) - } wantLangs := []string{"go"} if !reflect.DeepEqual(c.Langs, wantLangs) { diff --git a/walk/config.go b/walk/config.go index 8c59f967c..8255f8781 100644 --- a/walk/config.go +++ b/walk/config.go @@ -24,6 +24,7 @@ import ( "log" "os" "path" + "path/filepath" "strings" "github.com/bazelbuild/bazel-gazelle/config" @@ -73,18 +74,48 @@ func (wc *walkConfig) shouldFollow(p string) bool { var _ config.Configurer = (*Configurer)(nil) -type Configurer struct{} +type Configurer struct { + // Excludes and BUILD filenames specified on the command line. + // May be extending with BUILD directives. + cliExcludes []string + cliBuildFileNames string -func (*Configurer) RegisterFlags(fs *flag.FlagSet, cmd string, c *config.Config) { - wc := &walkConfig{} - c.Exts[walkName] = wc - fs.Var(&gzflag.MultiFlag{Values: &wc.excludes}, "exclude", "pattern that should be ignored (may be repeated)") + // Alternate BUILD read/write directories + readBuildFilesDir, writeBuildFilesDir string } -func (*Configurer) CheckFlags(fs *flag.FlagSet, c *config.Config) error { return nil } +func (wc *Configurer) RegisterFlags(fs *flag.FlagSet, cmd string, c *config.Config) { + fs.Var(&gzflag.MultiFlag{Values: &wc.cliExcludes}, "exclude", "pattern that should be ignored (may be repeated)") + fs.StringVar(&wc.cliBuildFileNames, "build_file_name", strings.Join(config.DefaultValidBuildFileNames, ","), "comma-separated list of valid build file names.\nThe first element of the list is the name of output build files to generate.") + fs.StringVar(&wc.readBuildFilesDir, "experimental_read_build_files_dir", "", "path to a directory where build files should be read from (instead of -repo_root)") + fs.StringVar(&wc.writeBuildFilesDir, "experimental_write_build_files_dir", "", "path to a directory where build files should be written to (instead of -repo_root)") +} + +func (wc *Configurer) CheckFlags(_ *flag.FlagSet, c *config.Config) error { + c.ValidBuildFileNames = strings.Split(wc.cliBuildFileNames, ",") + if wc.readBuildFilesDir != "" { + if filepath.IsAbs(wc.readBuildFilesDir) { + c.ReadBuildFilesDir = wc.readBuildFilesDir + } else { + c.ReadBuildFilesDir = filepath.Join(c.WorkDir, wc.readBuildFilesDir) + } + } + if wc.writeBuildFilesDir != "" { + if filepath.IsAbs(wc.writeBuildFilesDir) { + c.WriteBuildFilesDir = wc.writeBuildFilesDir + } else { + c.WriteBuildFilesDir = filepath.Join(c.WorkDir, wc.writeBuildFilesDir) + } + } + + c.Exts[walkName] = &walkConfig{ + excludes: wc.cliExcludes, + } + return nil +} func (*Configurer) KnownDirectives() []string { - return []string{"generation_mode", "exclude", "follow", "ignore"} + return []string{"build_file_name", "generation_mode", "exclude", "follow", "ignore"} } func (cr *Configurer) Configure(c *config.Config, rel string, f *rule.File) { @@ -96,6 +127,8 @@ func (cr *Configurer) Configure(c *config.Config, rel string, f *rule.File) { if f != nil { for _, d := range f.Directives { switch d.Key { + case "build_file_name": + c.ValidBuildFileNames = strings.Split(d.Value, ",") case "generation_mode": switch generationModeType(strings.TrimSpace(d.Value)) { case generationModeUpdate: diff --git a/walk/config_test.go b/walk/config_test.go index fbe343a6b..ce615d7c9 100644 --- a/walk/config_test.go +++ b/walk/config_test.go @@ -1,8 +1,14 @@ package walk import ( + "flag" + "os" + "path/filepath" + "reflect" "testing" + "github.com/bazelbuild/bazel-gazelle/config" + "github.com/bazelbuild/bazel-gazelle/rule" "github.com/bmatcuk/doublestar/v4" ) @@ -24,3 +30,53 @@ func TestCheckPathMatchPattern(t *testing.T) { } } } + +func TestConfigurerFlags(t *testing.T) { + dir, err := os.MkdirTemp(os.Getenv("TEST_TEMPDIR"), "config_test") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(dir) + dir, err = filepath.EvalSymlinks(dir) + if err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(dir, "WORKSPACE"), nil, 0o666); err != nil { + t.Fatal(err) + } + + c := config.New() + cc := &Configurer{} + fs := flag.NewFlagSet("test", flag.ContinueOnError) + cc.RegisterFlags(fs, "test", c) + args := []string{"-build_file_name", "x,y"} + if err := fs.Parse(args); err != nil { + t.Fatal(err) + } + if err := cc.CheckFlags(fs, c); err != nil { + t.Errorf("CheckFlags: %v", err) + } + + wantBuildFileNames := []string{"x", "y"} + if !reflect.DeepEqual(c.ValidBuildFileNames, wantBuildFileNames) { + t.Errorf("for ValidBuildFileNames, got %#v, want %#v", c.ValidBuildFileNames, wantBuildFileNames) + } +} + +func TestConfigurerDirectives(t *testing.T) { + c := config.New() + cc := &Configurer{} + buildData := []byte(`# gazelle:build_file_name x,y`) + f, err := rule.LoadData(filepath.Join("test", "BUILD.bazel"), "", buildData) + if err != nil { + t.Fatal(err) + } + if err := cc.CheckFlags(nil, c); err != nil { + t.Errorf("CheckFlags: %v", err) + } + cc.Configure(c, "", f) + want := []string{"x", "y"} + if !reflect.DeepEqual(c.ValidBuildFileNames, want) { + t.Errorf("for ValidBuildFileNames, got %#v, want %#v", c.ValidBuildFileNames, want) + } +} From ea4f6e0fe7ad4a9f780381bbefc948b0ee542dbe Mon Sep 17 00:00:00 2001 From: Jason Bedard Date: Tue, 4 Mar 2025 10:04:55 -0800 Subject: [PATCH 18/27] refactor: simplify walkDir signature (#2043) There's 2 commits here both simplifying it in different ways. **What type of PR is this?** Other **What package or component does this PR mostly affect?** all **What does this PR do? Why is it needed?** Cleanup regarding https://github.com/bazel-contrib/bazel-gazelle/pull/2039#discussion_r1964845284 --- walk/config.go | 74 +++++++++++++++++++++++++++++--------------------- walk/walk.go | 31 +++++++++------------ 2 files changed, 56 insertions(+), 49 deletions(-) diff --git a/walk/config.go b/walk/config.go index 8255f8781..0ec648d54 100644 --- a/walk/config.go +++ b/walk/config.go @@ -163,18 +163,48 @@ func (cr *Configurer) Configure(c *config.Config, rel string, f *rule.File) { c.Exts[walkName] = wcCopy } -type isIgnoredFunc = func(string) bool +type ignoreFilter struct { + ignoreDirectoryGlobs []string + ignorePaths map[string]struct{} +} + +func newIgnoreFilter(repoRoot string) *ignoreFilter { + bazelignorePaths, err := loadBazelIgnore(repoRoot) + if err != nil { + log.Printf("error loading .bazelignore: %v", err) + } + + repoDirectoryIgnores, err := loadRepoDirectoryIgnore(repoRoot) + if err != nil { + log.Printf("error loading REPO.bazel ignore_directories(): %v", err) + } + + return &ignoreFilter{ + ignorePaths: bazelignorePaths, + ignoreDirectoryGlobs: repoDirectoryIgnores, + } +} + +func (f *ignoreFilter) isDirectoryIgnored(p string) bool { + if _, ok := f.ignorePaths[p]; ok { + return true + } + return matchAnyGlob(f.ignoreDirectoryGlobs, p) +} -var nothingIgnored isIgnoredFunc = func(string) bool { return false } +func (f *ignoreFilter) isFileIgnored(p string) bool { + _, ok := f.ignorePaths[p] + return ok +} -func loadBazelIgnore(repoRoot string) (isIgnoredFunc, error) { +func loadBazelIgnore(repoRoot string) (map[string]struct{}, error) { ignorePath := path.Join(repoRoot, ".bazelignore") file, err := os.Open(ignorePath) if errors.Is(err, fs.ErrNotExist) { - return nothingIgnored, nil + return nil, nil } if err != nil { - return nothingIgnored, fmt.Errorf(".bazelignore exists but couldn't be read: %v", err) + return nil, fmt.Errorf(".bazelignore exists but couldn't be read: %v", err) } defer file.Close() @@ -200,27 +230,22 @@ func loadBazelIgnore(repoRoot string) (isIgnoredFunc, error) { excludes[ignore] = struct{}{} } - isBazelIgnored := func(p string) bool { - _, ok := excludes[p] - return ok - } - - return isBazelIgnored, nil + return excludes, nil } -func loadRepoDirectoryIgnore(repoRoot string) (isIgnoredFunc, error) { +func loadRepoDirectoryIgnore(repoRoot string) ([]string, error) { repoFilePath := path.Join(repoRoot, "REPO.bazel") repoFileContent, err := os.ReadFile(repoFilePath) if errors.Is(err, fs.ErrNotExist) { - return nothingIgnored, nil + return nil, nil } if err != nil { - return nothingIgnored, fmt.Errorf("REPO.bazel exists but couldn't be read: %v", err) + return nil, fmt.Errorf("REPO.bazel exists but couldn't be read: %v", err) } ast, err := bzl.Parse(repoRoot, repoFileContent) if err != nil { - return nothingIgnored, fmt.Errorf("failed to parse REPO.bazel: %v", err) + return nil, fmt.Errorf("failed to parse REPO.bazel: %v", err) } var ignoreDirectories []string @@ -230,12 +255,12 @@ func loadRepoDirectoryIgnore(repoRoot string) (isIgnoredFunc, error) { if call, isCall := expr.(*bzl.CallExpr); isCall { if inv, isIdentCall := call.X.(*bzl.Ident); isIdentCall && inv.Name == "ignore_directories" { if len(call.List) != 1 { - return nothingIgnored, fmt.Errorf("REPO.bazel ignore_directories() expects one argument") + return nil, fmt.Errorf("REPO.bazel ignore_directories() expects one argument") } list, isList := call.List[0].(*bzl.ListExpr) if !isList { - return nothingIgnored, fmt.Errorf("REPO.bazel ignore_directories() unexpected argument type: %T", call.List[0]) + return nil, fmt.Errorf("REPO.bazel ignore_directories() unexpected argument type: %T", call.List[0]) } for _, item := range list.List { @@ -255,20 +280,7 @@ func loadRepoDirectoryIgnore(repoRoot string) (isIgnoredFunc, error) { } } - if len(ignoreDirectories) == 0 { - return nothingIgnored, nil - } - - isRepoIgnored := func(p string) bool { - for _, ignore := range ignoreDirectories { - if doublestar.MatchUnvalidated(ignore, p) { - return true - } - } - return false - } - - return isRepoIgnored, nil + return ignoreDirectories, nil } func checkPathMatchPattern(pattern string) error { diff --git a/walk/walk.go b/walk/walk.go index 697d17346..af9217578 100644 --- a/walk/walk.go +++ b/walk/walk.go @@ -118,18 +118,9 @@ func Walk(c *config.Config, cexts []config.Configurer, dirs []string, mode Mode, } updateRels := NewUpdateFilter(c.RepoRoot, dirs, mode) + ignoreFilter := newIgnoreFilter(c.RepoRoot) - isBazelIgnored, err := loadBazelIgnore(c.RepoRoot) - if err != nil { - log.Printf("error loading .bazelignore: %v", err) - } - - isRepoDirectoryIgnored, err := loadRepoDirectoryIgnore(c.RepoRoot) - if err != nil { - log.Printf("error loading REPO.bazel ignore_directories(): %v", err) - } - - trie, err := buildTrie(c, updateRels, isBazelIgnored, isRepoDirectoryIgnored) + trie, err := buildTrie(c, updateRels, ignoreFilter) if err != nil { log.Fatalf("error walking the file system: %v\n", err) } @@ -396,7 +387,7 @@ func newTrie(entry fs.DirEntry) *pathTrie { } } -func buildTrie(c *config.Config, updateRels *UpdateFilter, isBazelIgnored, isRepoDirectoryIgnored isIgnoredFunc) (*pathTrie, error) { +func buildTrie(c *config.Config, updateRels *UpdateFilter, ignoreFilter *ignoreFilter) (*pathTrie, error) { trie := &pathTrie{} // A channel to limit the number of concurrent goroutines @@ -412,14 +403,14 @@ func buildTrie(c *config.Config, updateRels *UpdateFilter, isBazelIgnored, isRep // An error group to handle error propagation eg := errgroup.Group{} eg.Go(func() error { - return walkDir(c.RepoRoot, "", &eg, limitCh, updateRels, isBazelIgnored, isRepoDirectoryIgnored, trie) + return trie.walkDir(c.RepoRoot, "", &eg, limitCh, updateRels, ignoreFilter) }) return trie, eg.Wait() } // walkDir recursively and concurrently descends into the 'rel' directory and builds a trie -func walkDir(root, rel string, eg *errgroup.Group, limitCh chan struct{}, updateRels *UpdateFilter, isBazelIgnored, isRepoDirectoryIgnored isIgnoredFunc, trie *pathTrie) error { +func (trie *pathTrie) walkDir(root, rel string, eg *errgroup.Group, limitCh chan struct{}, updateRels *UpdateFilter, ignoreFilter *ignoreFilter) error { limitCh <- struct{}{} defer (func() { <-limitCh })() @@ -432,13 +423,13 @@ func walkDir(root, rel string, eg *errgroup.Group, limitCh chan struct{}, update entryName := entry.Name() entryPath := path.Join(rel, entryName) - // Ignore .git, empty names and ignored paths - if entryName == "" || entryName == ".git" || isBazelIgnored(entryPath) { + // Ignore .git and empty names + if entryName == "" || entryName == ".git" { continue } if entry.IsDir() { - if isRepoDirectoryIgnored(entryPath) { + if ignoreFilter.isDirectoryIgnored(entryPath) { continue } @@ -450,9 +441,13 @@ func walkDir(root, rel string, eg *errgroup.Group, limitCh chan struct{}, update entryTrie := newTrie(entry) trie.children = append(trie.children, entryTrie) eg.Go(func() error { - return walkDir(root, entryPath, eg, limitCh, updateRels, isBazelIgnored, isRepoDirectoryIgnored, entryTrie) + return entryTrie.walkDir(root, entryPath, eg, limitCh, updateRels, ignoreFilter) }) } else { + if ignoreFilter.isFileIgnored(entryPath) { + continue + } + trie.files = append(trie.files, entry) } } From f3d96444d8240beb1a7d46a8bbb59697de579154 Mon Sep 17 00:00:00 2001 From: Benjamin <871576+b-bremer@users.noreply.github.com> Date: Fri, 7 Mar 2025 22:52:44 +0100 Subject: [PATCH 19/27] Add proto override for k8s.io/kubelet to default_gazelle_overrides.bzl (#2049) **What type of PR is this?** Other **What package or component does this PR mostly affect?** cmd/gazelle **What does this PR do? Why is it needed?** Proto generation must be disabled as gogo/protobuf is broken and generated proto bindings are already available in the repo. **Which issues(s) does this PR fix?** **Other notes for review** --- internal/bzlmod/default_gazelle_overrides.bzl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/internal/bzlmod/default_gazelle_overrides.bzl b/internal/bzlmod/default_gazelle_overrides.bzl index 3c4a56370..1cff68eed 100644 --- a/internal/bzlmod/default_gazelle_overrides.bzl +++ b/internal/bzlmod/default_gazelle_overrides.bzl @@ -115,6 +115,9 @@ DEFAULT_DIRECTIVES_BY_PATH = { "gazelle:go_generate_proto false", "gazelle:proto_import_prefix k8s.io/apimachinery", ], + "k8s.io/kubelet": [ + "gazelle:proto disable", + ], "k8s.io/kubernetes": [ "gazelle:proto disable", ], From 23672a71fbf3d44305cc53280aa08dcb8c75be67 Mon Sep 17 00:00:00 2001 From: Jason Bedard Date: Thu, 20 Mar 2025 15:18:22 -0700 Subject: [PATCH 20/27] refactor: simplify symlink follow logic (#2054) The `fs.DirEntry` result was only ever used as a boolean and leads to confusion regarding what the return result should be used for. **What type of PR is this?** refactor **What package or component does this PR mostly affect?** all **What does this PR do? Why is it needed?** simplification --- walk/walk.go | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/walk/walk.go b/walk/walk.go index af9217578..5503ee70c 100644 --- a/walk/walk.go +++ b/walk/walk.go @@ -174,7 +174,7 @@ func visit(c *config.Config, cexts []config.Configurer, knownDirectives map[stri if wc.isExcluded(entRel) { continue } - if ent := resolveFileInfo(wc, dir, entRel, ent); ent != nil { + if shouldFollow(wc, dir, entRel, ent) { regularFiles = append(regularFiles, base) } } @@ -189,7 +189,7 @@ func visit(c *config.Config, cexts []config.Configurer, knownDirectives map[stri if wc.isExcluded(entRel) { continue } - if ent := resolveFileInfo(wc, dir, entRel, t.entry); ent != nil { + if shouldFollow(wc, dir, entRel, t.entry) { if updateRels.shouldVisit(entRel, shouldUpdate) { subFiles, shouldMerge := visit(c.Clone(), cexts, knownDirectives, updateRels, t, wf, entRel, shouldUpdate) if shouldMerge { @@ -357,21 +357,20 @@ func findGenFiles(wc *walkConfig, f *rule.File) []string { return genFiles } -func resolveFileInfo(wc *walkConfig, dir, rel string, ent fs.DirEntry) fs.DirEntry { +func shouldFollow(wc *walkConfig, dir, rel string, ent fs.DirEntry) bool { if ent.Type()&os.ModeSymlink == 0 { - // Not a symlink, use the original FileInfo. - return ent + // Not a symlink + return true } if !wc.shouldFollow(rel) { // A symlink, but not one we should follow. - return nil + return false } - fi, err := os.Stat(path.Join(dir, ent.Name())) - if err != nil { + if _, err := os.Stat(path.Join(dir, ent.Name())); err != nil { // A symlink, but not one we could resolve. - return nil + return false } - return fs.FileInfoToDirEntry(fi) + return true } type pathTrie struct { From 186298911d38850b47b198e8d933a93125ce7043 Mon Sep 17 00:00:00 2001 From: peter woodman Date: Sat, 22 Mar 2025 05:52:55 -0400 Subject: [PATCH 21/27] fetch_repo: also remove WORKSPACE and MODULE files when cleaning (#1991) **What type of PR is this?** Feature **What package or component does this PR mostly affect?** go_repository **What does this PR do? Why is it needed?** Sometimes it's not just build files in go modules that are trouble, but inconveniently constructed MODULE.bazel files in repositories you'd like to pinch some go code from can also ruin your day. This does away with them, as well as WORKSPACE files for good measure. **Which issues(s) does this PR fix?** **Other notes for review** --- cmd/fetch_repo/clean.go | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/cmd/fetch_repo/clean.go b/cmd/fetch_repo/clean.go index f5f0c625c..331661039 100644 --- a/cmd/fetch_repo/clean.go +++ b/cmd/fetch_repo/clean.go @@ -22,6 +22,15 @@ import ( ) func cleanBuildFiles(path string) error { + filenamesToClean := []string{ + "BUILD", + "BUILD.bazel", + "MODULE.bazel", + "MODULE.bazel.lock", + "WORKSPACE", + "WORKSPACE.bazel", + "WORKSPACE.bzlmod", + } return filepath.Walk(*dest, func(path string, info fs.FileInfo, err error) error { if err != nil { return err @@ -29,8 +38,10 @@ func cleanBuildFiles(path string) error { if info.IsDir() { return nil } - if info.Name() == "BUILD" || info.Name() == "BUILD.bazel" { - return os.Remove(path) + for _, filename := range filenamesToClean { + if info.Name() == filename { + return os.Remove(path) + } } return nil }) From 3abbe5ae1918dc83f622849854a80a7961413bc2 Mon Sep 17 00:00:00 2001 From: Alex Lopez Date: Wed, 26 Mar 2025 19:47:52 +0100 Subject: [PATCH 22/27] Improve performance of "Evaluate build tags as true and false" feature (#2058) This PR provides an alternative way of implementing #1938 (i.e. evaluating both `tag` and `!tag` as true when evaluating build constraints). The way it was implemented originally has exponential time complexity with the number of provided build tags. This made gazelle unusable on projects with a relatively large number of tags, as the run would never end. This implementation leverages the existing `dropNegationForIgnoredTags` to manipulate the tag expression. Fixes #1262 in a more efficient way than what #1938 implemented. --- language/go/build_constraints.go | 99 +++++++++------------------ language/go/build_constraints_test.go | 5 +- language/go/config.go | 35 +++------- language/go/config_test.go | 27 +++----- language/go/fileinfo.go | 27 +++++--- language/go/fileinfo_test.go | 27 ++++---- 6 files changed, 83 insertions(+), 137 deletions(-) diff --git a/language/go/build_constraints.go b/language/go/build_constraints.go index 8bae3ce30..4d681aab8 100644 --- a/language/go/build_constraints.go +++ b/language/go/build_constraints.go @@ -52,7 +52,7 @@ func readTags(path string) (*buildTags, error) { return nil, err } - return newBuildTags(x) + return newBuildTags(x), nil } var fullConstraint constraint.Expr @@ -88,7 +88,7 @@ func readTags(path string) (*buildTags, error) { return nil, nil } - return newBuildTags(fullConstraint) + return newBuildTags(fullConstraint), nil } // buildTags represents the build tags specified in a file. @@ -103,21 +103,14 @@ type buildTags struct { // newBuildTags will return a new buildTags structure with any // ignored tags filtered out from the provided constraints. -func newBuildTags(x constraint.Expr) (*buildTags, error) { - modified, err := dropNegationForIgnoredTags(pushNot(x, false)) - if err != nil { - return nil, err - } - - rawTags, err := collectTags(modified) - if err != nil { - return nil, err - } +func newBuildTags(x constraint.Expr) *buildTags { + modified := dropNegationForIgnoredTags(pushNot(x, false), isDefaultIgnoredTag) + rawTags := collectTags(modified) return &buildTags{ expr: modified, rawTags: rawTags, - }, nil + } } func (b *buildTags) tags() []string { @@ -149,16 +142,16 @@ func (b *buildTags) empty() bool { // without having to worry that the result will be negated later on. Ignored tags should always // evaluate to true, regardless of whether they are negated or not leaving the final evaluation // to happen at compile time by the compiler. -func dropNegationForIgnoredTags(expr constraint.Expr) (constraint.Expr, error) { +func dropNegationForIgnoredTags(expr constraint.Expr, isIgnoredTag func(tag string) bool) constraint.Expr { if expr == nil { - return nil, nil + return nil } switch x := expr.(type) { case *constraint.TagExpr: return &constraint.TagExpr{ Tag: x.Tag, - }, nil + } case *constraint.NotExpr: var toRet constraint.Expr @@ -168,58 +161,40 @@ func dropNegationForIgnoredTags(expr constraint.Expr) (constraint.Expr, error) { Tag: tag.Tag, } } else { - fixed, err := dropNegationForIgnoredTags(x.X) - if err != nil { - return nil, err - } + fixed := dropNegationForIgnoredTags(x.X, isIgnoredTag) toRet = &constraint.NotExpr{X: fixed} } - return toRet, nil + return toRet case *constraint.AndExpr: - a, err := dropNegationForIgnoredTags(x.X) - if err != nil { - return nil, err - } - - b, err := dropNegationForIgnoredTags(x.Y) - if err != nil { - return nil, err - } + a := dropNegationForIgnoredTags(x.X, isIgnoredTag) + b := dropNegationForIgnoredTags(x.Y, isIgnoredTag) return &constraint.AndExpr{ X: a, Y: b, - }, nil - - case *constraint.OrExpr: - a, err := dropNegationForIgnoredTags(x.X) - if err != nil { - return nil, err - } - - b, err := dropNegationForIgnoredTags(x.Y) - if err != nil { - return nil, err } + case *constraint.OrExpr: + a := dropNegationForIgnoredTags(x.X, isIgnoredTag) + b := dropNegationForIgnoredTags(x.Y, isIgnoredTag) return &constraint.OrExpr{ X: a, Y: b, - }, nil + } default: - return nil, fmt.Errorf("unknown constraint type: %T", x) + panic(fmt.Errorf("unknown constraint type: %T", x)) } } -// filterTags will traverse the provided constraint.Expr, recursively, and call +// visitTags will traverse the provided constraint.Expr, recursively, and call // the user provided ok func on concrete constraint.TagExpr structures. If the provided // func returns true, the tag in question is kept, otherwise it is filtered out. -func visitTags(expr constraint.Expr, visit func(string)) (err error) { +func visitTags(expr constraint.Expr, visit func(string)) { if expr == nil { - return nil + return } switch x := expr.(type) { @@ -227,37 +202,29 @@ func visitTags(expr constraint.Expr, visit func(string)) (err error) { visit(x.Tag) case *constraint.NotExpr: - err = visitTags(x.X, visit) + visitTags(x.X, visit) case *constraint.AndExpr: - err = visitTags(x.X, visit) - if err == nil { - err = visitTags(x.Y, visit) - } + visitTags(x.X, visit) + visitTags(x.Y, visit) case *constraint.OrExpr: - err = visitTags(x.X, visit) - if err == nil { - err = visitTags(x.Y, visit) - } + visitTags(x.X, visit) + visitTags(x.Y, visit) default: - return fmt.Errorf("unknown constraint type: %T", x) + panic(fmt.Errorf("unknown constraint type: %T", x)) } return } -func collectTags(expr constraint.Expr) ([]string, error) { +func collectTags(expr constraint.Expr) []string { var tags []string - err := visitTags(expr, func(tag string) { + visitTags(expr, func(tag string) { tags = append(tags, tag) }) - if err != nil { - return nil, err - } - - return tags, err + return tags } // cgoTagsAndOpts contains compile or link options which should only be applied @@ -304,15 +271,15 @@ func matchAuto(tokens []string) (*buildTags, error) { return nil, err } - return newBuildTags(x) + return newBuildTags(x), nil } -// isIgnoredTag returns whether the tag is "cgo", "purego", "race", "msan" or is a release tag. +// isDefaultIgnoredTag returns whether the tag is "cgo", "purego", "race", "msan" or is a release tag. // Release tags match the pattern "go[0-9]\.[0-9]+". // Gazelle won't consider whether an ignored tag is satisfied when evaluating // build constraints for a file and will instead defer to the compiler at compile // time. -func isIgnoredTag(tag string) bool { +func isDefaultIgnoredTag(tag string) bool { if tag == "cgo" || tag == "purego" || tag == "race" || tag == "msan" { return true } diff --git a/language/go/build_constraints_test.go b/language/go/build_constraints_test.go index fc311828a..680b7ab38 100644 --- a/language/go/build_constraints_test.go +++ b/language/go/build_constraints_test.go @@ -60,10 +60,7 @@ func TestFilterBuildTags(t *testing.T) { }, } { t.Run(tc.desc, func(t *testing.T) { - bt, err := newBuildTags(tc.input) - if err != nil { - t.Fatal(err) - } + bt := newBuildTags(tc.input) if diff := cmp.Diff(tc.want, bt.expr); diff != "" { t.Errorf("(-want, +got): %s", diff) } diff --git a/language/go/config.go b/language/go/config.go index 89ac3f4d1..ac7f089ff 100644 --- a/language/go/config.go +++ b/language/go/config.go @@ -41,16 +41,6 @@ import ( var minimumRulesGoVersion = version.Version{0, 29, 0} -type tagSet map[string]struct{} - -func (ts tagSet) clone() tagSet { - c := make(tagSet, len(ts)) - for k, v := range ts { - c[k] = v - } - return c -} - // goConfig contains configuration values related to Go rules. type goConfig struct { // The name under which the rules_go repository can be referenced from the @@ -63,7 +53,7 @@ type goConfig struct { // genericTags is a set of tags that Gazelle considers to be true. Set with // -build_tags or # gazelle:build_tags. Some tags, like gc, are always on. - genericTags []tagSet + genericTags map[string]bool // prefix is a prefix of an import path, used to generate importpath // attributes. Set with -go_prefix or # gazelle:prefix. @@ -188,10 +178,12 @@ func newGoConfig() *goConfig { goProtoCompilers: defaultGoProtoCompilers, goGrpcCompilers: defaultGoGrpcCompilers, goGenerateProto: true, - genericTags: []tagSet{ - {"gc": struct{}{}}, - }, } + if gc.genericTags == nil { + gc.genericTags = make(map[string]bool) + } + // Add default tags + gc.genericTags["gc"] = true return gc } @@ -201,9 +193,9 @@ func getGoConfig(c *config.Config) *goConfig { func (gc *goConfig) clone() *goConfig { gcCopy := *gc - gcCopy.genericTags = make([]tagSet, 0, len(gc.genericTags)) - for _, ts := range gc.genericTags { - gcCopy.genericTags = append(gcCopy.genericTags, ts.clone()) + gcCopy.genericTags = make(map[string]bool) + for k, v := range gc.genericTags { + gcCopy.genericTags[k] = v } gcCopy.goProtoCompilers = gc.goProtoCompilers[:len(gc.goProtoCompilers):len(gc.goProtoCompilers)] gcCopy.goGrpcCompilers = gc.goGrpcCompilers[:len(gc.goGrpcCompilers):len(gc.goGrpcCompilers)] @@ -221,13 +213,7 @@ func (gc *goConfig) setBuildTags(tags string) error { if strings.HasPrefix(t, "!") { return fmt.Errorf("build tags can't be negated: %s", t) } - var newSets []tagSet - for _, ts := range gc.genericTags { - c := ts.clone() - c[t] = struct{}{} - newSets = append(newSets, c) - } - gc.genericTags = append(gc.genericTags, newSets...) + gc.genericTags[t] = true } return nil } @@ -594,6 +580,7 @@ Update io_bazel_rules_go to a newer version in your WORKSPACE file.` case "build_tags": if err := gc.setBuildTags(d.Value); err != nil { log.Print(err) + continue } case "go_generate_proto": diff --git a/language/go/config_test.go b/language/go/config_test.go index 3ac54d88e..e239c1165 100644 --- a/language/go/config_test.go +++ b/language/go/config_test.go @@ -59,21 +59,6 @@ func testConfig(t *testing.T, args ...string) (*config.Config, []language.Langua return c, langs, cexts } -func newTagSet(tags ...string) tagSet { - ts := make(tagSet) - for _, t := range tags { - ts[t] = struct{}{} - } - return ts -} - -var expectedBuildTags = []tagSet{ - newTagSet("gc"), - newTagSet("gc", "foo"), - newTagSet("gc", "bar"), - newTagSet("gc", "foo", "bar"), -} - func TestCommandLine(t *testing.T) { c, _, _ := testConfig( t, @@ -83,8 +68,10 @@ func TestCommandLine(t *testing.T) { "-external=vendored", "-repo_root=.") gc := getGoConfig(c) - if diff := cmp.Diff(expectedBuildTags, gc.genericTags); diff != "" { - t.Errorf("(-want, +got): %s", diff) + for _, tag := range []string{"foo", "bar", "gc"} { + if !gc.genericTags[tag] { + t.Errorf("expected tag %q to be set", tag) + } } if gc.prefix != "example.com/repo" { t.Errorf(`got prefix %q; want "example.com/repo"`, gc.prefix) @@ -114,8 +101,10 @@ func TestDirectives(t *testing.T) { cext.Configure(c, "test", f) } gc := getGoConfig(c) - if diff := cmp.Diff(expectedBuildTags, gc.genericTags); diff != "" { - t.Errorf("(-want, +got): %s", diff) + for _, tag := range []string{"foo", "bar", "gc"} { + if !gc.genericTags[tag] { + t.Errorf("expected tag %q to be set", tag) + } } if gc.prefix != "y" { t.Errorf(`got prefix %q; want "y"`, gc.prefix) diff --git a/language/go/fileinfo.go b/language/go/fileinfo.go index 1d5d7c924..a4c75f98b 100644 --- a/language/go/fileinfo.go +++ b/language/go/fileinfo.go @@ -569,8 +569,19 @@ func checkConstraints(c *config.Config, os, arch, osSuffix, archSuffix string, t } goConf := getGoConfig(c) - checker := func(tag string, ts tagSet) bool { - if isIgnoredTag(tag) { + + if tags != nil { + // Treat provided generic tags as "ignored tags", meaning that both + // `tag` and `!tag` are considered true when evaluating build constraints + isIgnoredTag := func(tag string) bool { + return goConf.genericTags[tag] + } + + tags = newBuildTags(dropNegationForIgnoredTags(tags.expr, isIgnoredTag)) + } + + checker := func(tag string) bool { + if isDefaultIgnoredTag(tag) { return true } if _, ok := rule.KnownOSSet[tag]; ok || tag == "unix" { @@ -585,19 +596,13 @@ func checkConstraints(c *config.Config, os, arch, osSuffix, archSuffix string, t return false } return arch == tag + } - _, ok := ts[tag] - return ok + return goConf.genericTags[tag] } - for _, ts := range goConf.genericTags { - c := func(tag string) bool { return checker(tag, ts) } - if tags.eval(c) && cgoTags.eval(c) { - return true - } - } - return false + return tags.eval(checker) && cgoTags.eval(checker) } // rulesGoSupportsOS returns whether the os tag is recognized by the version of diff --git a/language/go/fileinfo_test.go b/language/go/fileinfo_test.go index 6c89ac650..9aac4afc3 100644 --- a/language/go/fileinfo_test.go +++ b/language/go/fileinfo_test.go @@ -386,7 +386,7 @@ func TestCheckConstraints(t *testing.T) { defer os.RemoveAll(dir) for _, tc := range []struct { desc string - genericTags string + genericTags map[string]bool os, arch, filename, content string want bool }{ @@ -466,12 +466,12 @@ func TestCheckConstraints(t *testing.T) { want: false, }, { desc: "tags all satisfied", - genericTags: "a,b", + genericTags: map[string]bool{"a": true, "b": true}, content: "// +build a,b\n\npackage foo", want: true, }, { desc: "tags some satisfied", - genericTags: "a", + genericTags: map[string]bool{"a": true}, content: "// +build a,b\n\npackage foo", want: false, }, { @@ -480,7 +480,7 @@ func TestCheckConstraints(t *testing.T) { want: true, }, { desc: "tag satisfied negated", - genericTags: "a", + genericTags: map[string]bool{"a": true}, content: "// +build !a\n\npackage foo", want: true, }, { @@ -489,27 +489,27 @@ func TestCheckConstraints(t *testing.T) { want: false, }, { desc: "tag group and satisfied", - genericTags: "foo,bar", + genericTags: map[string]bool{"foo": true, "bar": true}, content: "// +build foo,bar\n\npackage foo", want: true, }, { desc: "tag group and unsatisfied", - genericTags: "foo", + genericTags: map[string]bool{"foo": true}, content: "// +build foo,bar\n\npackage foo", want: false, }, { desc: "tag line or satisfied", - genericTags: "foo", + genericTags: map[string]bool{"foo": true}, content: "// +build foo bar\n\npackage foo", want: true, }, { desc: "tag line or unsatisfied", - genericTags: "foo", + genericTags: map[string]bool{"foo": true}, content: "// +build !foo bar\n\npackage foo", want: true, }, { desc: "tag lines and satisfied", - genericTags: "foo,bar", + genericTags: map[string]bool{"foo": true, "bar": true}, content: ` // +build foo // +build bar @@ -518,7 +518,7 @@ package foo`, want: true, }, { desc: "tag lines and unsatisfied", - genericTags: "foo", + genericTags: map[string]bool{"foo": true}, content: ` // +build foo // +build bar @@ -528,7 +528,7 @@ package foo`, }, { desc: "cgo tags satisfied", os: "linux", - genericTags: "foo", + genericTags: map[string]bool{"foo": true}, content: ` // +build foo @@ -581,8 +581,9 @@ import "C" t.Run(tc.desc, func(t *testing.T) { c, _, _ := testConfig(t) gc := getGoConfig(c) - if err := gc.setBuildTags(tc.genericTags); err != nil { - t.Errorf("error setting build tags %q", tc.genericTags) + gc.genericTags = tc.genericTags + if gc.genericTags == nil { + gc.genericTags = map[string]bool{"gc": true} } filename := tc.filename if filename == "" { From f31fd97eb8e499e9470c4db012f6a14f9ab088eb Mon Sep 17 00:00:00 2001 From: Tyler French Date: Thu, 3 Apr 2025 15:03:33 -0400 Subject: [PATCH 23/27] feat(update-repos): allow users to profile gazelle update-repos (#2056) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR enables CPU and Memory profiling capabilities in `gazelle update-repos` Resolves #2057 Tested locally with `gazelle update-repos` ![Screenshot 2025-03-21 at 12 54 31 PM](https://github.com/user-attachments/assets/ee8a0f12-3971-434b-a425-ee4809fb482e) --- cmd/gazelle/update-repos.go | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/cmd/gazelle/update-repos.go b/cmd/gazelle/update-repos.go index 3a418a0d1..5e8e71493 100644 --- a/cmd/gazelle/update-repos.go +++ b/cmd/gazelle/update-repos.go @@ -20,6 +20,7 @@ import ( "errors" "flag" "fmt" + "log" "os" "path/filepath" "sort" @@ -42,6 +43,9 @@ type updateReposConfig struct { pruneRules bool workspace *rule.File repoFileMap map[string]*rule.File + cpuProfile string + memProfile string + profile profiler } const updateReposName = "_update-repos" @@ -82,10 +86,19 @@ func (*updateReposConfigurer) RegisterFlags(fs *flag.FlagSet, cmd string, c *con fs.StringVar(&uc.repoFilePath, "from_file", "", "Gazelle will translate repositories listed in this file into repository rules in WORKSPACE or a .bzl macro function. Gopkg.lock and go.mod files are supported") fs.Var(macroFlag{macroFileName: &uc.macroFileName, macroDefName: &uc.macroDefName}, "to_macro", "Tells Gazelle to write repository rules into a .bzl macro function rather than the WORKSPACE file. . The expected format is: macroFile%defName") fs.BoolVar(&uc.pruneRules, "prune", false, "When enabled, Gazelle will remove rules that no longer have equivalent repos in the go.mod file. Can only used with -from_file.") + + fs.StringVar(&uc.cpuProfile, "cpuprofile", "", "write cpu profile to `file`") + fs.StringVar(&uc.memProfile, "memprofile", "", "write memory profile to `file`") } func (*updateReposConfigurer) CheckFlags(fs *flag.FlagSet, c *config.Config) error { uc := getUpdateReposConfig(c) + p, err := newProfiler(uc.cpuProfile, uc.memProfile) + if err != nil { + return err + } + uc.profile = p + switch { case uc.repoFilePath != "": if len(fs.Args()) != 0 { @@ -105,7 +118,6 @@ func (*updateReposConfigurer) CheckFlags(fs *flag.FlagSet, c *config.Config) err uc.importPaths = fs.Args() } - var err error workspacePath := wspace.FindWORKSPACEFile(c.RepoRoot) uc.workspace, err = rule.LoadWorkspaceFile(workspacePath, "") if err != nil { @@ -141,6 +153,11 @@ func updateRepos(wd string, args []string) (err error) { return err } uc := getUpdateReposConfig(c) + defer func() { + if err := uc.profile.stop(); err != nil { + log.Printf("stopping profiler: %v", err) + } + }() kinds := make(map[string]rule.KindInfo) loads := []rule.LoadInfo{} From da5862990bcd2fed209899df4e0b6168d72906ef Mon Sep 17 00:00:00 2001 From: Utkarsh Chanchlani Date: Thu, 10 Apr 2025 02:30:18 -0700 Subject: [PATCH 24/27] Add proto override for github.com/hashicorp/go-plugin to default_gazelle_overrides.bzl (#2062) **What type of PR is this?** Other **What package or component does this PR mostly affect?** cmd/gazelle **What does this PR do? Why is it needed?** Proto generation must be disabled as github.com/hashicorp/go-plugin is broken and generated proto bindings are already available in the repo. **Which issues(s) does this PR fix?** **Other notes for review** Co-authored-by: Utkarsh Chanchlani --- internal/bzlmod/default_gazelle_overrides.bzl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/internal/bzlmod/default_gazelle_overrides.bzl b/internal/bzlmod/default_gazelle_overrides.bzl index 1cff68eed..ca2a406dd 100644 --- a/internal/bzlmod/default_gazelle_overrides.bzl +++ b/internal/bzlmod/default_gazelle_overrides.bzl @@ -85,6 +85,9 @@ DEFAULT_DIRECTIVES_BY_PATH = { "gazelle:go_naming_convention import_alias", "gazelle:proto disable", ], + "github.com/hashicorp/go-plugin": [ + "gazelle:proto disable", + ], "github.com/prometheus/alertmanager": [ "gazelle:proto disable", ], From ba7eba9a2dfd50e5ca090e5eebd49f59f9b1e432 Mon Sep 17 00:00:00 2001 From: Philipp Stephani Date: Sun, 13 Apr 2025 22:46:40 +0200 Subject: [PATCH 25/27] =?UTF-8?q?Support=20(and=20ignore)=20=E2=80=98tool?= =?UTF-8?q?=E2=80=99=20directive=20in=20go.mod=20files.=20(#2064)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit See https://go.dev/doc/modules/gomod-ref#tool and https://go.dev/doc/go1.24#tools. Fixes #2031 **What type of PR is this?** Feature **What package or component does this PR mostly affect?** all **What does this PR do? Why is it needed?** Don't fail parsing of go.mod if it contains a tool directive. **Which issues(s) does this PR fix?** Fixes #2031 --- internal/bzlmod/go_mod.bzl | 2 +- tests/bzlmod/go_mod_test.bzl | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/internal/bzlmod/go_mod.bzl b/internal/bzlmod/go_mod.bzl index 30c2bd044..df1058a36 100644 --- a/internal/bzlmod/go_mod.bzl +++ b/internal/bzlmod/go_mod.bzl @@ -210,7 +210,7 @@ def parse_go_mod(content, path): continue if not current_directive: - if tokens[0] not in ["module", "go", "require", "replace", "exclude", "retract", "toolchain"]: + if tokens[0] not in ["module", "go", "require", "replace", "exclude", "retract", "toolchain", "tool"]: fail("{}:{}: unexpected token '{}' at start of line".format(path, line_no, tokens[0])) if len(tokens) == 1: fail("{}:{}: expected another token after '{}'".format(path, line_no, tokens[0])) diff --git a/tests/bzlmod/go_mod_test.bzl b/tests/bzlmod/go_mod_test.bzl index c05ba03a1..de2b62341 100644 --- a/tests/bzlmod/go_mod_test.bzl +++ b/tests/bzlmod/go_mod_test.bzl @@ -25,6 +25,8 @@ module github.com/bazelbuild/bazel-gazelle retract v1.0.0 require golang.org/x/sys v0.0.0-20220624220833-87e55d714810 // indirect + +tool golang.org/x/tools/cmd/bisect """ _EXPECTED_GO_MOD_PARSE_RESULT = struct( From e59910c5c1a3845491602eeec22986d2e7dd24b4 Mon Sep 17 00:00:00 2001 From: Stefan Penner Date: Mon, 14 Apr 2025 10:34:17 -0600 Subject: [PATCH 26/27] [Feature] Enable support for go.work files in non-root modules (#2067) **What type of PR is this?** Feature **What package or component does this PR mostly affect?** language/go **What does this PR do? Why is it needed?** As part of #1731, we disabled multiple go.work files due to only time constraints and uncertainty about its implications. After thorough consideration and nearly a year of running with this removed without issues, I feel comfortable enabling it. Reasoning: Since go.work files are essentially rollups of go.mod files, and we already support multiple go.mod files, extending support to go.work files is a natural and safe progression. **Which issues(s) does this PR fix?** **Other notes for review** --- internal/bzlmod/go_deps.bzl | 3 --- 1 file changed, 3 deletions(-) diff --git a/internal/bzlmod/go_deps.bzl b/internal/bzlmod/go_deps.bzl index 6e57e84d8..bfa5bcfe3 100644 --- a/internal/bzlmod/go_deps.bzl +++ b/internal/bzlmod/go_deps.bzl @@ -400,9 +400,6 @@ def _go_deps_impl(module_ctx): if from_file_tag.go_mod: from_file_tags.append(from_file_tag) elif from_file_tag.go_work: - if module.is_root != True: - fail("go_deps.from_file(go_work = '{}') tag can only be used from a root module but: '{}' is not a root module.".format(from_file_tag.go_work, module.name)) - go_work = go_work_from_label(module_ctx, from_file_tag.go_work) # this ensures go.work replacements are considered From d7a25a22834345b1648298723a6d2ec3a41442f0 Mon Sep 17 00:00:00 2001 From: Holger Freyther Date: Tue, 15 Apr 2025 00:40:13 +0800 Subject: [PATCH 27/27] Improve consistency by prefering bazel_dep over Go (#2065) Letting the bazel_dep and Go dependency participate equally in the version resolution is creating various inconsistencies. This can lead to to a root module having to either use `inject_repo` or `use_repo` and `override_repo`. Resolve this by consistently preferring the `bazel_dep` over the Go dependency. Keep the existing code that warns (or can fail) if the dependencies are at different versions. Example MODULE.bazel: ``` module( name = "bazel_dep_should_win", ) bazel_dep(name = "rules_go", version = "0.53.0") bazel_dep(name = "gazelle", version = "0.42.0") bazel_dep(name = "circl", version = "1.3.8") go_deps = use_extension("@gazelle//:extensions.bzl", "go_deps") go_deps.from_file(go_mod = "//:go.mod") ``` And the go.mod referring to a newer version. Fixes #2060 **What type of PR is this?** > Uncomment one line below and remove others. > > Bug fix > Feature > Documentation > Other **What package or component does this PR mostly affect?** > For example: > > language/go > cmd/gazelle > go_repository > all **What does this PR do? Why is it needed?** **Which issues(s) does this PR fix?** Fixes # **Other notes for review** --- internal/bzlmod/go_deps.bzl | 9 ++------- tests/bcr/go_mod/go.mod | 2 +- tests/bcr/go_mod/go.sum | 2 ++ tests/bcr/go_work/pkg/go.mod | 2 +- tests/bcr/go_work/pkg/go.sum | 2 ++ 5 files changed, 8 insertions(+), 9 deletions(-) diff --git a/internal/bzlmod/go_deps.bzl b/internal/bzlmod/go_deps.bzl index bfa5bcfe3..d22a6aa3a 100644 --- a/internal/bzlmod/go_deps.bzl +++ b/internal/bzlmod/go_deps.bzl @@ -563,9 +563,8 @@ def _go_deps_impl(module_ctx): bazel_dep_is_older = path in module_resolutions and bazel_dep.version < module_resolutions[path].version - # Version mismatches between the Go module and the bazel_dep can confuse Go tooling. If the bazel_dep version - # is lower, it won't be used, which can result in unexpected builds and should thus always be reported, even for - # indirect deps. Explicitly overridden modules are not reported as this requires manual action. + # Version mismatches between the Go module and the bazel_dep are problematic. For consistency always + # prefer the bazel_dep version and report any mismatch to the user. if (path in module_resolutions and bazel_dep.version != module_resolutions[path].version and bazel_dep.version != _HIGHEST_VERSION_SENTINEL and @@ -617,10 +616,6 @@ Mismatch between versions requested for Go module {module}: go_module_version = go_module_version, ), *remediation) - # Only use the Bazel module if it is at least as high as the required Go module version. - if bazel_dep_is_older: - continue - # TODO: We should update root_versions if the bazel_dep is a direct dependency of the root # module. However, we currently don't have a way to determine that. module_resolutions[path] = bazel_dep diff --git a/tests/bcr/go_mod/go.mod b/tests/bcr/go_mod/go.mod index 0b6325c1d..f58274c6c 100644 --- a/tests/bcr/go_mod/go.mod +++ b/tests/bcr/go_mod/go.mod @@ -13,7 +13,7 @@ require ( github.com/bazelbuild/rules_go v0.39.1 // NOTE: keep <4.7.0 to test the 'replace' github.com/bmatcuk/doublestar/v4 v4.6.0 - github.com/cloudflare/circl v1.3.7 + github.com/cloudflare/circl v1.6.1 github.com/envoyproxy/protoc-gen-validate v1.0.1 github.com/fmeum/dep_on_gazelle v1.0.0 github.com/google/go-jsonnet v0.20.0 diff --git a/tests/bcr/go_mod/go.sum b/tests/bcr/go_mod/go.sum index bdbc4f8b0..7429603c4 100644 --- a/tests/bcr/go_mod/go.sum +++ b/tests/bcr/go_mod/go.sum @@ -17,6 +17,8 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= +github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= +github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= diff --git a/tests/bcr/go_work/pkg/go.mod b/tests/bcr/go_work/pkg/go.mod index d97377231..82e428251 100644 --- a/tests/bcr/go_work/pkg/go.mod +++ b/tests/bcr/go_work/pkg/go.mod @@ -9,7 +9,7 @@ require ( github.com/bazelbuild/rules_go v0.44.0 // NOTE: keep <4.7.0 to test the 'replace' github.com/bmatcuk/doublestar/v4 v4.6.1 - github.com/cloudflare/circl v1.3.7 + github.com/cloudflare/circl v1.6.1 github.com/envoyproxy/protoc-gen-validate v1.0.4 github.com/fmeum/dep_on_gazelle v1.0.0 github.com/google/safetext v0.0.0-20240104143208-7a7d9b3d812f diff --git a/tests/bcr/go_work/pkg/go.sum b/tests/bcr/go_work/pkg/go.sum index c5d9f6e1b..193850008 100644 --- a/tests/bcr/go_work/pkg/go.sum +++ b/tests/bcr/go_work/pkg/go.sum @@ -17,6 +17,8 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= +github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= +github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=