From 50c92e35616eb95c26193ff131d46e4b9ff1517c Mon Sep 17 00:00:00 2001 From: Scott Moeller Date: Fri, 16 Jan 2026 17:23:32 +0000 Subject: [PATCH] feat(gazelle): respect gazelle:resolve buf directives for cross resolution Addresses #117. Add support for `gazelle:resolve buf` directives in the buf gazelle plugin's `CrossResolve` function. This allows users to override the default `@buf_deps//` resolution for proto imports, enabling proper integration with `local_path_override` modules in bzlmod monorepos. The `CrossResolve` function now first checks for `gazelle:resolve buf` directives using the standard `resolve.FindRuleWithOverride` function. If a matching directive is found, return that label. Otherwise, fall back to the default `@buf_deps//` resolution. Example Usage ```python This directive goes in a BUILD file (typically root or parent directory) and tells gazelle to resolve imports of proto/foo/bar.proto to the label @com_example//proto/foo:bar_proto instead of the default @buf_deps//... resolution. ``` Test Coverage ``` bazel test //... ``` Co-Authored-By: Claude Opus 4.5 --- gazelle/buf/buf_test.go | 5 +++++ gazelle/buf/cross_resolve.go | 17 +++++++++++++++-- .../cross_resolve_override/buf.work.yaml | 4 ++++ .../cross_resolve_override/fooapis/BUILD.in | 1 + .../cross_resolve_override/fooapis/BUILD.out | 10 ++++++++++ .../cross_resolve_override/fooapis/buf.yaml | 7 +++++++ .../fooapis/foo/v1/BUILD.in | 0 .../fooapis/foo/v1/BUILD.out | 15 +++++++++++++++ .../fooapis/foo/v1/foo.proto | 5 +++++ .../cross_resolve_override/petapis/BUILD.in | 2 ++ .../cross_resolve_override/petapis/BUILD.out | 11 +++++++++++ .../cross_resolve_override/petapis/buf.yaml | 7 +++++++ .../petapis/pets/v1/BUILD.in | 0 .../petapis/pets/v1/BUILD.out | 19 +++++++++++++++++++ .../petapis/pets/v1/pets.proto | 10 ++++++++++ 15 files changed, 111 insertions(+), 2 deletions(-) create mode 100644 gazelle/buf/testdata/cross_resolve_override/buf.work.yaml create mode 100644 gazelle/buf/testdata/cross_resolve_override/fooapis/BUILD.in create mode 100644 gazelle/buf/testdata/cross_resolve_override/fooapis/BUILD.out create mode 100644 gazelle/buf/testdata/cross_resolve_override/fooapis/buf.yaml create mode 100644 gazelle/buf/testdata/cross_resolve_override/fooapis/foo/v1/BUILD.in create mode 100644 gazelle/buf/testdata/cross_resolve_override/fooapis/foo/v1/BUILD.out create mode 100644 gazelle/buf/testdata/cross_resolve_override/fooapis/foo/v1/foo.proto create mode 100644 gazelle/buf/testdata/cross_resolve_override/petapis/BUILD.in create mode 100644 gazelle/buf/testdata/cross_resolve_override/petapis/BUILD.out create mode 100644 gazelle/buf/testdata/cross_resolve_override/petapis/buf.yaml create mode 100644 gazelle/buf/testdata/cross_resolve_override/petapis/pets/v1/BUILD.in create mode 100644 gazelle/buf/testdata/cross_resolve_override/petapis/pets/v1/BUILD.out create mode 100644 gazelle/buf/testdata/cross_resolve_override/petapis/pets/v1/pets.proto diff --git a/gazelle/buf/buf_test.go b/gazelle/buf/buf_test.go index 0fd5f3e..7ed9e05 100644 --- a/gazelle/buf/buf_test.go +++ b/gazelle/buf/buf_test.go @@ -65,6 +65,11 @@ func TestCrossResolve(t *testing.T) { testRunGazelle(t, "v2/cross_resolve") } +func TestCrossResolveOverride(t *testing.T) { + t.Parallel() + testRunGazelle(t, "cross_resolve_override") +} + func TestMerge(t *testing.T) { t.Parallel() testRunGazelle(t, "merge") diff --git a/gazelle/buf/cross_resolve.go b/gazelle/buf/cross_resolve.go index 9425778..a3cb1a4 100644 --- a/gazelle/buf/cross_resolve.go +++ b/gazelle/buf/cross_resolve.go @@ -33,8 +33,21 @@ func (*bufLang) CrossResolve(gazelleConfig *config.Config, ruleIndex *resolve.Ru if langWithDep != "proto" || importSpec.Lang != "proto" { return nil } - config := GetConfigForGazelleConfig(gazelleConfig) - depRepo := getRepoNameForPath(config.BufConfigFile.Pkg) + + // First, check for gazelle:resolve buf directives + // This allows users to override the default buf_deps resolution + // Example: # gazelle:resolve buf proto/foo/bar.proto @com_example//proto/foo:bar_proto + bufImportSpec := resolve.ImportSpec{ + Lang: "buf", + Imp: importSpec.Imp, + } + if override, ok := resolve.FindRuleWithOverride(gazelleConfig, bufImportSpec, "buf"); ok { + return []resolve.FindResult{{Label: override}} + } + + // Fall back to default buf_deps resolution + bufConfig := GetConfigForGazelleConfig(gazelleConfig) + depRepo := getRepoNameForPath(bufConfig.BufConfigFile.Pkg) return []resolve.FindResult{ { Label: label.New(depRepo, path.Dir(importSpec.Imp), proto.RuleName(path.Dir(importSpec.Imp))), diff --git a/gazelle/buf/testdata/cross_resolve_override/buf.work.yaml b/gazelle/buf/testdata/cross_resolve_override/buf.work.yaml new file mode 100644 index 0000000..ab2cba5 --- /dev/null +++ b/gazelle/buf/testdata/cross_resolve_override/buf.work.yaml @@ -0,0 +1,4 @@ +version: v1 +directories: + - fooapis + - petapis diff --git a/gazelle/buf/testdata/cross_resolve_override/fooapis/BUILD.in b/gazelle/buf/testdata/cross_resolve_override/fooapis/BUILD.in new file mode 100644 index 0000000..c1d725b --- /dev/null +++ b/gazelle/buf/testdata/cross_resolve_override/fooapis/BUILD.in @@ -0,0 +1 @@ +# gazelle:buf_breaking_against //:against_file diff --git a/gazelle/buf/testdata/cross_resolve_override/fooapis/BUILD.out b/gazelle/buf/testdata/cross_resolve_override/fooapis/BUILD.out new file mode 100644 index 0000000..a04122f --- /dev/null +++ b/gazelle/buf/testdata/cross_resolve_override/fooapis/BUILD.out @@ -0,0 +1,10 @@ +load("@rules_buf//buf:defs.bzl", "buf_breaking_test") + +# gazelle:buf_breaking_against //:against_file + +buf_breaking_test( + name = "buf_breaking", + against = "//:against_file", + config = "//fooapis:buf.yaml", + targets = ["//fooapis/foo/v1:foo_v1_proto"], +) diff --git a/gazelle/buf/testdata/cross_resolve_override/fooapis/buf.yaml b/gazelle/buf/testdata/cross_resolve_override/fooapis/buf.yaml new file mode 100644 index 0000000..1a51945 --- /dev/null +++ b/gazelle/buf/testdata/cross_resolve_override/fooapis/buf.yaml @@ -0,0 +1,7 @@ +version: v1 +breaking: + use: + - FILE +lint: + use: + - DEFAULT diff --git a/gazelle/buf/testdata/cross_resolve_override/fooapis/foo/v1/BUILD.in b/gazelle/buf/testdata/cross_resolve_override/fooapis/foo/v1/BUILD.in new file mode 100644 index 0000000..e69de29 diff --git a/gazelle/buf/testdata/cross_resolve_override/fooapis/foo/v1/BUILD.out b/gazelle/buf/testdata/cross_resolve_override/fooapis/foo/v1/BUILD.out new file mode 100644 index 0000000..73feea5 --- /dev/null +++ b/gazelle/buf/testdata/cross_resolve_override/fooapis/foo/v1/BUILD.out @@ -0,0 +1,15 @@ +load("@rules_proto//proto:defs.bzl", "proto_library") +load("@rules_buf//buf:defs.bzl", "buf_lint_test") + +proto_library( + name = "foo_v1_proto", + srcs = ["foo.proto"], + strip_import_prefix = "/fooapis", + visibility = ["//visibility:public"], +) + +buf_lint_test( + name = "foo_v1_proto_lint", + config = "//fooapis:buf.yaml", + targets = [":foo_v1_proto"], +) diff --git a/gazelle/buf/testdata/cross_resolve_override/fooapis/foo/v1/foo.proto b/gazelle/buf/testdata/cross_resolve_override/fooapis/foo/v1/foo.proto new file mode 100644 index 0000000..403aebb --- /dev/null +++ b/gazelle/buf/testdata/cross_resolve_override/fooapis/foo/v1/foo.proto @@ -0,0 +1,5 @@ +syntax = "proto3"; + +package foo.v1; + +message Foo {} diff --git a/gazelle/buf/testdata/cross_resolve_override/petapis/BUILD.in b/gazelle/buf/testdata/cross_resolve_override/petapis/BUILD.in new file mode 100644 index 0000000..12d4e1a --- /dev/null +++ b/gazelle/buf/testdata/cross_resolve_override/petapis/BUILD.in @@ -0,0 +1,2 @@ +# gazelle:buf_breaking_against //:against_file +# gazelle:resolve buf validate/validate.proto @custom_validate//validate:validate_proto diff --git a/gazelle/buf/testdata/cross_resolve_override/petapis/BUILD.out b/gazelle/buf/testdata/cross_resolve_override/petapis/BUILD.out new file mode 100644 index 0000000..110aa97 --- /dev/null +++ b/gazelle/buf/testdata/cross_resolve_override/petapis/BUILD.out @@ -0,0 +1,11 @@ +load("@rules_buf//buf:defs.bzl", "buf_breaking_test") + +# gazelle:buf_breaking_against //:against_file +# gazelle:resolve buf validate/validate.proto @custom_validate//validate:validate_proto + +buf_breaking_test( + name = "buf_breaking", + against = "//:against_file", + config = "//petapis:buf.yaml", + targets = ["//petapis/pets/v1:pets_v1_proto"], +) diff --git a/gazelle/buf/testdata/cross_resolve_override/petapis/buf.yaml b/gazelle/buf/testdata/cross_resolve_override/petapis/buf.yaml new file mode 100644 index 0000000..1a51945 --- /dev/null +++ b/gazelle/buf/testdata/cross_resolve_override/petapis/buf.yaml @@ -0,0 +1,7 @@ +version: v1 +breaking: + use: + - FILE +lint: + use: + - DEFAULT diff --git a/gazelle/buf/testdata/cross_resolve_override/petapis/pets/v1/BUILD.in b/gazelle/buf/testdata/cross_resolve_override/petapis/pets/v1/BUILD.in new file mode 100644 index 0000000..e69de29 diff --git a/gazelle/buf/testdata/cross_resolve_override/petapis/pets/v1/BUILD.out b/gazelle/buf/testdata/cross_resolve_override/petapis/pets/v1/BUILD.out new file mode 100644 index 0000000..66483d8 --- /dev/null +++ b/gazelle/buf/testdata/cross_resolve_override/petapis/pets/v1/BUILD.out @@ -0,0 +1,19 @@ +load("@rules_proto//proto:defs.bzl", "proto_library") +load("@rules_buf//buf:defs.bzl", "buf_lint_test") + +proto_library( + name = "pets_v1_proto", + srcs = ["pets.proto"], + strip_import_prefix = "/petapis", + visibility = ["//visibility:public"], + deps = [ + "//fooapis/foo/v1:foo_v1_proto", + "@custom_validate//validate:validate_proto", + ], +) + +buf_lint_test( + name = "pets_v1_proto_lint", + config = "//petapis:buf.yaml", + targets = [":pets_v1_proto"], +) diff --git a/gazelle/buf/testdata/cross_resolve_override/petapis/pets/v1/pets.proto b/gazelle/buf/testdata/cross_resolve_override/petapis/pets/v1/pets.proto new file mode 100644 index 0000000..3329c54 --- /dev/null +++ b/gazelle/buf/testdata/cross_resolve_override/petapis/pets/v1/pets.proto @@ -0,0 +1,10 @@ +syntax = "proto3"; + +package pets.v1; + +import "foo/v1/foo.proto"; +import "validate/validate.proto"; + +message Pet { + foo.v1.Foo foo = 1; +}