Skip to content

Commit cfd51a8

Browse files
authored
[proto tooling] add hermetic protoc compilation managed by bazel (#201)
## Summary Pin the entire codegen toolchain so a single `make proto` produces byte-identical output on any host. Previously, `make proto` was dependent on host-installed versions of protoc + plugins which led to different output when run on different machine. ## Test Plan ✅ `make fmt && make proto && make build && make test && make e2e-test` ✅ `make proto` <-- no changes to generated source ✅ Modify .proto -> call `make proto` -> see expected updates to all `protopb/*` files ## Issues T3-CODE-204
1 parent ed52df8 commit cfd51a8

9 files changed

Lines changed: 166 additions & 29 deletions

File tree

MODULE.bazel

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ bazel_dep(name = "rules_proto", version = "7.1.0")
99
# dependencies) from source on every cold build. Requires
1010
# --incompatible_enable_proto_toolchain_resolution (set in .bazelrc).
1111
protoc = use_extension("@toolchains_protoc//protoc:extensions.bzl", "protoc")
12+
protoc.toolchain(
13+
version = "v29.3",
14+
)
1215
use_repo(protoc, "toolchains_protoc_hub")
1316

1417
register_toolchains("@toolchains_protoc_hub//:all")
@@ -37,6 +40,11 @@ go_sdk.host()
3740

3841
go_deps = use_extension("@gazelle//:extensions.bzl", "go_deps")
3942
go_deps.from_file(go_mod = "//:go.mod")
43+
go_deps.module(
44+
path = "google.golang.org/grpc/cmd/protoc-gen-go-grpc",
45+
sum = "h1:F29+wU6Ee6qgu9TddPgooOdaqsxTMunOoj8KA5yuS5A=",
46+
version = "v1.5.1",
47+
)
4048

4149
# All *direct* Go dependencies of the module have to be listed explicitly
4250
use_repo(
@@ -49,6 +57,7 @@ use_repo(
4957
"com_github_uber_go_tally_v4",
5058
"in_gopkg_yaml_v3",
5159
"org_golang_google_grpc",
60+
"org_golang_google_grpc_cmd_protoc_gen_go_grpc",
5261
"org_golang_google_protobuf",
5362
"org_golang_x_oauth2",
5463
"org_uber_go_fx",

Makefile

Lines changed: 15 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ YAMLFMT_VERSION ?= v0.16.0
2626
# goimports version for Go formatting + import fixing
2727
GOIMPORTS_VERSION ?= v0.33.0
2828

29+
# Proto packages: <domain>/<service> dirs whose protopb/ holds the generated
30+
# stubs. Each is generated by Bazel into bazel-bin/tool/proto/<domain>_<service>/
31+
# (the out_dir convention in tool/proto/BUILD.bazel) and copied back here.
32+
PROTO_PACKAGES = submitqueue/gateway submitqueue/orchestrator stovepipe/gateway stovepipe/orchestrator
33+
2934
# Set REPO_ROOT for docker-compose
3035
export REPO_ROOT := $(shell pwd)
3136

@@ -106,10 +111,7 @@ clean: ## Clean generated files and binaries
106111

107112
clean-proto: ## Clean generated proto files
108113
@echo "Cleaning generated proto files..."
109-
@rm -rf submitqueue/gateway/protopb/*.pb.go
110-
@rm -rf submitqueue/orchestrator/protopb/*.pb.go
111-
@rm -rf stovepipe/gateway/protopb/*.pb.go
112-
@rm -rf stovepipe/orchestrator/protopb/*.pb.go
114+
@rm -f $(foreach p,$(PROTO_PACKAGES),$(p)/protopb/*.pb.go $(p)/protopb/*.pb.yarpc.go)
113115
@echo "Proto clean complete!"
114116

115117
deps: tidy-go ## Download and tidy Go dependencies
@@ -338,23 +340,15 @@ mocks: ## Generate mock files using mockgen
338340
@echo "Mocks generated successfully!"
339341

340342
proto: ## Generate protobuf files from .proto definitions
341-
@echo "Generating protobuf files with protoc..."
342-
@protoc --go_out=submitqueue/gateway/protopb --go_opt=paths=source_relative \
343-
--go-grpc_out=submitqueue/gateway/protopb --go-grpc_opt=paths=source_relative \
344-
--yarpc-go_out=submitqueue/gateway/protopb --yarpc-go_opt=paths=source_relative \
345-
--proto_path=submitqueue/gateway/proto submitqueue/gateway/proto/gateway.proto
346-
@protoc --go_out=submitqueue/orchestrator/protopb --go_opt=paths=source_relative \
347-
--go-grpc_out=submitqueue/orchestrator/protopb --go-grpc_opt=paths=source_relative \
348-
--yarpc-go_out=submitqueue/orchestrator/protopb --yarpc-go_opt=paths=source_relative \
349-
--proto_path=submitqueue/orchestrator/proto submitqueue/orchestrator/proto/orchestrator.proto
350-
@protoc --go_out=stovepipe/gateway/protopb --go_opt=paths=source_relative \
351-
--go-grpc_out=stovepipe/gateway/protopb --go-grpc_opt=paths=source_relative \
352-
--yarpc-go_out=stovepipe/gateway/protopb --yarpc-go_opt=paths=source_relative \
353-
--proto_path=stovepipe/gateway/proto stovepipe/gateway/proto/gateway.proto
354-
@protoc --go_out=stovepipe/orchestrator/protopb --go_opt=paths=source_relative \
355-
--go-grpc_out=stovepipe/orchestrator/protopb --go-grpc_opt=paths=source_relative \
356-
--yarpc-go_out=stovepipe/orchestrator/protopb --yarpc-go_opt=paths=source_relative \
357-
--proto_path=stovepipe/orchestrator/proto stovepipe/orchestrator/proto/orchestrator.proto
343+
@echo "Generating protobuf files with Bazel..."
344+
@$(BAZEL) build //tool/proto:generated
345+
@set -e; for pkg in $(PROTO_PACKAGES); do \
346+
out=$$(echo $$pkg | tr / _); base=$$(basename $$pkg); \
347+
for f in $$base.pb.go $$base.pb.yarpc.go $${base}_grpc.pb.go; do \
348+
cp -f bazel-bin/tool/proto/$$out/$$f $$pkg/protopb/$$f; \
349+
done; \
350+
done
351+
@$(BAZEL) run @rules_go//go -- run golang.org/x/tools/cmd/goimports@$(GOIMPORTS_VERSION) -w $(addsuffix /protopb,$(PROTO_PACKAGES))
358352
@echo "Protobuf files generated successfully!"
359353

360354
# Bazel query helpers

doc/howto/DEVELOPMENT.md

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -86,12 +86,7 @@ GoLand works with Go modules automatically. Open the project root and GoLand wil
8686

8787
```bash
8888
# macOS
89-
brew install protobuf grpcurl
90-
91-
# Go protoc plugins (only if modifying .proto files)
92-
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
93-
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
94-
go install go.uber.org/yarpc/encoding/protobuf/protoc-gen-yarpc-go@latest
89+
brew install grpcurl
9590
```
9691

9792
## Common Make Targets
@@ -138,8 +133,8 @@ See [TESTING.md](TESTING.md) for the full testing guide, including integration a
138133
## Troubleshooting
139134

140135
**Proto generation fails:**
141-
- Ensure all three protoc plugins are installed (see Optional Tools above)
142-
- Check that `protoc` is in your PATH: `which protoc`
136+
- Run `make proto`; Bazel provides the pinned `protoc` and plugin toolchain.
137+
- If Bazel cannot fetch tools, check network access and the repository cache configuration in `.bazelrc`.
143138

144139
**Build fails after proto changes:**
145140
- Run `make proto` to regenerate proto files

stovepipe/gateway/proto/BUILD.bazel

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@ load("@rules_go//go:def.bzl", "go_library")
22
load("@rules_go//proto:def.bzl", "go_proto_library")
33
load("@rules_proto//proto:defs.bzl", "proto_library")
44

5+
exports_files(
6+
["gateway.proto"],
7+
visibility = ["//tool/proto:__pkg__"],
8+
)
9+
510
proto_library(
611
name = "gatewaypb_proto",
712
srcs = ["gateway.proto"],

stovepipe/orchestrator/proto/BUILD.bazel

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@ load("@rules_go//go:def.bzl", "go_library")
22
load("@rules_go//proto:def.bzl", "go_proto_library")
33
load("@rules_proto//proto:defs.bzl", "proto_library")
44

5+
exports_files(
6+
["orchestrator.proto"],
7+
visibility = ["//tool/proto:__pkg__"],
8+
)
9+
510
proto_library(
611
name = "orchestratorpb_proto",
712
srcs = ["orchestrator.proto"],

submitqueue/gateway/proto/BUILD.bazel

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@ load("@rules_go//go:def.bzl", "go_library")
22
load("@rules_go//proto:def.bzl", "go_proto_library")
33
load("@rules_proto//proto:defs.bzl", "proto_library")
44

5+
exports_files(
6+
["gateway.proto"],
7+
visibility = ["//tool/proto:__pkg__"],
8+
)
9+
510
proto_library(
611
name = "gatewaypb_proto",
712
srcs = ["gateway.proto"],

submitqueue/orchestrator/proto/BUILD.bazel

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@ load("@rules_go//go:def.bzl", "go_library")
22
load("@rules_go//proto:def.bzl", "go_proto_library")
33
load("@rules_proto//proto:defs.bzl", "proto_library")
44

5+
exports_files(
6+
["orchestrator.proto"],
7+
visibility = ["//tool/proto:__pkg__"],
8+
)
9+
510
proto_library(
611
name = "orchestratorpb_proto",
712
srcs = ["orchestrator.proto"],

tool/proto/BUILD.bazel

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
load("//tool/proto:proto_codegen.bzl", "go_proto_generated_files")
2+
3+
go_proto_generated_files(
4+
name = "submitqueue_gateway",
5+
src = "//submitqueue/gateway/proto:gateway.proto",
6+
out_dir = "submitqueue_gateway",
7+
)
8+
9+
go_proto_generated_files(
10+
name = "submitqueue_orchestrator",
11+
src = "//submitqueue/orchestrator/proto:orchestrator.proto",
12+
out_dir = "submitqueue_orchestrator",
13+
)
14+
15+
go_proto_generated_files(
16+
name = "stovepipe_gateway",
17+
src = "//stovepipe/gateway/proto:gateway.proto",
18+
out_dir = "stovepipe_gateway",
19+
)
20+
21+
go_proto_generated_files(
22+
name = "stovepipe_orchestrator",
23+
src = "//stovepipe/orchestrator/proto:orchestrator.proto",
24+
out_dir = "stovepipe_orchestrator",
25+
)
26+
27+
filegroup(
28+
name = "generated",
29+
srcs = [
30+
":stovepipe_gateway",
31+
":stovepipe_orchestrator",
32+
":submitqueue_gateway",
33+
":submitqueue_orchestrator",
34+
],
35+
)

tool/proto/proto_codegen.bzl

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
"""Hermetic protobuf code generation helpers."""
2+
3+
def _strip_proto_suffix(filename):
4+
if not filename.endswith(".proto"):
5+
fail("expected a .proto source, got {}".format(filename))
6+
return filename[:-len(".proto")]
7+
8+
def _go_proto_generated_files_impl(ctx):
9+
src = ctx.file.src
10+
base = _strip_proto_suffix(src.basename)
11+
12+
out_dir = ctx.attr.out_dir
13+
outputs = [
14+
ctx.actions.declare_file("{}/{}.pb.go".format(out_dir, base)),
15+
ctx.actions.declare_file("{}/{}.pb.yarpc.go".format(out_dir, base)),
16+
ctx.actions.declare_file("{}/{}_grpc.pb.go".format(out_dir, base)),
17+
]
18+
19+
proto_toolchain = ctx.toolchains["@rules_proto//proto:toolchain_type"].proto
20+
protoc = proto_toolchain.proto_compiler
21+
22+
args = ctx.actions.args()
23+
for opt in proto_toolchain.protoc_opts:
24+
args.add(opt)
25+
args.add("--plugin=protoc-gen-go={}".format(ctx.executable._protoc_gen_go.path))
26+
args.add("--plugin=protoc-gen-go-grpc={}".format(ctx.executable._protoc_gen_go_grpc.path))
27+
args.add("--plugin=protoc-gen-yarpc-go={}".format(ctx.executable._protoc_gen_yarpc_go.path))
28+
args.add("--go_out={}".format(outputs[0].dirname))
29+
args.add("--go_opt=paths=source_relative")
30+
args.add("--go-grpc_out={}".format(outputs[0].dirname))
31+
args.add("--go-grpc_opt=paths=source_relative")
32+
args.add("--yarpc-go_out={}".format(outputs[0].dirname))
33+
args.add("--yarpc-go_opt=paths=source_relative")
34+
args.add("--proto_path={}".format(src.dirname))
35+
args.add(src.path)
36+
37+
tools = [
38+
protoc,
39+
ctx.executable._protoc_gen_go,
40+
ctx.executable._protoc_gen_go_grpc,
41+
ctx.executable._protoc_gen_yarpc_go,
42+
]
43+
44+
ctx.actions.run_shell(
45+
inputs = [src],
46+
outputs = outputs,
47+
tools = tools,
48+
command = "mkdir -p {out_dir} && {protoc} \"$@\"".format(
49+
out_dir = outputs[0].dirname,
50+
protoc = protoc.executable.path,
51+
),
52+
arguments = [args],
53+
mnemonic = "HermeticGoProto",
54+
progress_message = "Generating Go protobuf files for {}".format(src.short_path),
55+
)
56+
57+
return [DefaultInfo(files = depset(outputs))]
58+
59+
go_proto_generated_files = rule(
60+
implementation = _go_proto_generated_files_impl,
61+
attrs = {
62+
"src": attr.label(
63+
allow_single_file = [".proto"],
64+
mandatory = True,
65+
),
66+
"out_dir": attr.string(mandatory = True),
67+
"_protoc_gen_go": attr.label(
68+
default = "@org_golang_google_protobuf//cmd/protoc-gen-go",
69+
executable = True,
70+
cfg = "exec",
71+
),
72+
"_protoc_gen_go_grpc": attr.label(
73+
default = "@org_golang_google_grpc_cmd_protoc_gen_go_grpc//:protoc-gen-go-grpc",
74+
executable = True,
75+
cfg = "exec",
76+
),
77+
"_protoc_gen_yarpc_go": attr.label(
78+
default = "@org_uber_go_yarpc//encoding/protobuf/protoc-gen-yarpc-go",
79+
executable = True,
80+
cfg = "exec",
81+
),
82+
},
83+
toolchains = ["@rules_proto//proto:toolchain_type"],
84+
)

0 commit comments

Comments
 (0)