Order dependency design bug - Build failure with a gRPC + Go + Python project
What version of rules_go are you using?
rules_go-v0.34.0
What version of gazelle are you using?
Unsure - whatever version is implied by rules_go v0.34.0, Go version 1.19.1 and the combination of protocol buffers and gRPC I tried to use. I'm including my WORKSPACE file in its entirety below.
What version of Bazel are you using?
5.3.0
Does this issue reproduce with the latest releases of all the above?
I believe I am on the latest releases of everything.
What operating system and processor architecture are you using?
$ uname -a
Linux <REDACTED> 5.4.0-1087-gcp #95~18.04.1-Ubuntu SMP Mon Aug 22 03:26:39 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux
Any other potentially useful information about your toolchain?
I'm setting up a repository from scratch, trying to use
- Bazel
- Protocol buffers + gRPC
- Go on the server
- Python on the client
What did you do?
Take the following WORKSPACE file:
workspace(name = "project-foo")
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
# https://github.com/bazelbuild/rules_proto
http_archive(
name = "rules_proto",
sha256 = "e017528fd1c91c5a33f15493e3a398181a9e821a804eb7ff5acdd1d2d6c2b18d",
strip_prefix = "rules_proto-4.0.0-3.20.0",
urls = [
"https://github.com/bazelbuild/rules_proto/archive/refs/tags/4.0.0-3.20.0.tar.gz",
],
)
load("@rules_proto//proto:repositories.bzl", "rules_proto_dependencies", "rules_proto_toolchains")
rules_proto_dependencies()
rules_proto_toolchains()
# https://github.com/bazelbuild/rules_go
http_archive(
name = "io_bazel_rules_go",
sha256 = "16e9fca53ed6bd4ff4ad76facc9b7b651a89db1689a2877d6fd7b82aa824e366",
urls = [
"https://mirror.bazel.build/github.com/bazelbuild/rules_go/releases/download/v0.34.0/rules_go-v0.34.0.zip",
"https://github.com/bazelbuild/rules_go/releases/download/v0.34.0/rules_go-v0.34.0.zip",
],
)
load("@io_bazel_rules_go//go:deps.bzl", "go_download_sdk", "go_register_toolchains", "go_rules_dependencies")
go_download_sdk(
name = "go_sdk",
version = "1.19.1",
)
go_rules_dependencies()
go_register_toolchains()
# https://github.com/rules-proto-grpc/rules_proto_grpc
http_archive(
name = "rules_proto_grpc",
sha256 = "bbe4db93499f5c9414926e46f9e35016999a4e9f6e3522482d3760dc61011070",
strip_prefix = "rules_proto_grpc-4.2.0",
urls = ["https://github.com/rules-proto-grpc/rules_proto_grpc/archive/4.2.0.tar.gz"],
)
# https://rules-proto-grpc.com/en/latest/#installation
load("@rules_proto_grpc//:repositories.bzl", "bazel_gazelle", "rules_proto_grpc_repos", "rules_proto_grpc_toolchains")
rules_proto_grpc_toolchains()
rules_proto_grpc_repos()
# https://rules-proto-grpc.com/en/latest/lang/go.html
bazel_gazelle()
load("@rules_proto_grpc//go:repositories.bzl", rules_proto_grpc_go_repos = "go_repos")
rules_proto_grpc_go_repos()
load("@bazel_gazelle//:deps.bzl", "gazelle_dependencies")
gazelle_dependencies()
# https://rules-proto-grpc.com/en/latest/lang/python.html#python-grpc-library
load("@rules_proto_grpc//python:repositories.bzl", rules_proto_grpc_python_repos = "python_repos")
rules_proto_grpc_python_repos()
load("@com_github_grpc_grpc//bazel:grpc_deps.bzl", rules_proto_grpc_python_deps = "grpc_deps")
rules_proto_grpc_python_deps()
load("@com_github_grpc_grpc//bazel:grpc_extra_deps.bzl", rules_proto_grpc_python_extra_deps = "grpc_extra_deps")
rules_proto_grpc_python_extra_deps()
Add a BUILD file with the following contents:
load("@rules_proto//proto:defs.bzl", "proto_library")
load("@rules_proto_grpc//go:defs.bzl", "go_grpc_library")
load("@rules_proto_grpc//python:defs.bzl", "python_grpc_library")
proto_library(
name = "project_foo_proto",
srcs = ["project_foo.proto"],
visibility = ["//visibility:public"],
)
go_grpc_library(
name = "project_foo_go_proto",
importpath = "github.com/company/project-foo",
protos = [":project_foo_proto"],
visibility = ["//visibility:public"],
)
python_grpc_library(
name = "project_foo_py_proto",
protos = [":project_foo_proto"],
visibility = ["//visibility:public"],
)
Create a minimal project_foo.proto with a request message, a response message and a service definition with one method using the two messages.
Run bazel build :all. (bazelisk build will also do).
What did you expect to see?
Successful build.
What did you see instead?
ERROR: Traceback (most recent call last):
File "/home/ovidiu.platon/src/project-foo/WORKSPACE", line 85, column 35, in <toplevel>
rules_proto_grpc_python_extra_deps()
File "/home/ovidiu.platon/.cache/bazel/_bazel_ovidiu.platon/499be9f2b6984f13f37570e2aac3fba9/external/com_github_grpc_grpc/bazel/grpc_extra_deps.bzl", line 56, column 27, in grpc_extra_deps
go_register_toolchains(version = "1.18")
File "/home/ovidiu.platon/.cache/bazel/_bazel_ovidiu.platon/499be9f2b6984f13f37570e2aac3fba9/external/io_bazel_rules_go/go/private/sdk.bzl", line 419, column 13, in go_register_toolchains
fail("go_register_toolchains: version set after go sdk rule declared ({})".format(", ".join([r["name"] for r in sdk_rules])))
Error in fail: go_register_toolchains: version set after go sdk rule declared (go_sdk)
It appears that the approach to go_sdk and go_register_toolchains is fragile and order dependent. The first component to specify a Go version prevents all others from specifying anything at all. Even worse, even if my WORKSPACE file specifies the same version as the gRPC build rules in grpc/grpc - grpc/bazel/grpc_extra_deps.bzl, the check in go_register_toolchains rejects everything. A less fragile approach that gives control to the WORKSPACE writer, and that prevents rule authors from falling into this trap would be a lot better.
I also filed https://github.com/grpc/grpc/issues/30965 with grpc/grpc. I think both projects have their part in this situation - the Go Bazel rules should figure out a different strategy for toolchain version unification (or for multiple toolchain versions, whatever is more appropriate), and other Bazel rules that depend on Go internally should not choose on the WORKSPACE user's behalf.
The principled solution for this mess is Bzlmod. Given how stretched everyone is and how brittle WORKSPACE semantics are, I'm not sure whether we can substantially improve this situation. Incremental improvements of the WORKSPACE situation may still be possible and I'm happy to help with turning ideas into PRs.
Thank you for the prompt answer! I'll try Bzlmod first. If that doesn't work out, maybe we can find a solution that's not too terrible for WORKSPACE rules. (I understand the constraints of the space, so it won't be pretty...)
Just keep in mind that Bzlmod and even more so the rules_go module are still experimental. If you find bugs, I will happily fix them though.
I made very limited progress with Bzlmod. I'll continue pursuing this path, though it might be an easier sell when it's officially launched.
Due to schedule pressure, I solved this in a "peak innovation" kind of way: I ended up creating 3 WORKSPACE files, in a pretty intuitive configuration:
- The first one for the protocol buffers shared between the client and the server. Only has
proto_librarydefinitions. - For the Go server, deals with the gRPC + Go toolchains.
- For the Python client, deals with the gRPC + Python toolchains.
This way, the Go SDK doesn't end up in conflict with the Python gRPC toolchain (which uses Go internally). 2 and 3 don't really depend on each other in any way, either. Both refer to 1 through local_repository with a relative ../proto path.
If it's stupid and it works, it's not all that stupid, I guess.
@ovidiupl Thanks for the hint -- Could you share some example of that approach? Stuck with the same problem as you, but not being successful with that approach either.
In particular, when I try to reference the proto_library on 1 from 2, I t also depends on grpc, so I end up again with both conflicting dependencies again.
On my case, I want py3_image (which uses go) and grpc, hence the conflict.