From https://www.reddit.com/r/golang/comments/1kwklgf/comment/mui4zkq/
Without a tool like buf, it's a lot of work for the consumers.
Let's say you leave the project layout exactly as in your question:
my-library/
└─ directory1/shared.proto
so downstream code can keep writing
import "directory1/shared.proto";
and nothing looks unfamiliar in the .proto
files themselves. The trick is to hand protoc
an include path that always points to the current copy of your module, whatever version go.mod
happens to require right now. go list
can tell you where that copy lives:
PROTO\_ROOT=$(go list -f '{{.Dir}}' -m github.com/author1/my-library)
protoc \\
-I . \\
-I "$PROTO\_ROOT" \\
--go\_out=. \\
--go\_opt=paths=source\_relative \\
proto/myproject/my\_service.proto
Because go list -m
consults the consumer’s go.mod
, there’s no hard-coded version number: when someone runs go get -u
, the path changes automatically.
Inside your directory1/shared.proto
, make sure you already have a go_package
line that matches the Go import path of the generated file you ship:
option go\_package = "github.com/author1/my-library/directory1;sharedpb";
That single line ensures the consumer’s generated code imports sharedpb "github.com/author1/my-library/directory1"
and not some stale package.
What about editors? Most Proto-aware IDEs won’t magically read the shell script above, so they still complain about the import until you give them the same include path. A one-liner in a dev-container, a .vscode/settings.json
, or even an environment variable like
export PROTO\_PATHS="-I $(go list -f '{{.Dir}}' -m github.com/author1/my-library)"
is usually enough to silence the squiggles. If you want zero configuration inside editors, Buf really is the only drop-in answer for now—but that’s the extra tool you were trying to avoid.
Finally, scalability: every additional module that carries .proto
files needs one more -I $(go list ... )
. Teams with many such deps usually pick one of three routes:
-
run
go mod vendor
and just pass-I vendor
, -
generate a little symlink tree from
go list -m all
, or -
bite the bullet and adopt Buf (or Bazel) so the wrapper tool discovers everything for them.
If you’re happy staying on raw protoc
, the pattern above—go list -m
for the path, a correct go_package
, and a bit of IDE plumbing—is about as smooth as it gets without introducing new build machinery.