Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save StevenACoffman/e35a05e443033dfc7bad7cead061f69d to your computer and use it in GitHub Desktop.
Save StevenACoffman/e35a05e443033dfc7bad7cead061f69d to your computer and use it in GitHub Desktop.
Importing proto files from an external Go library

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment