Skip to content

Commit 103669e

Browse files
committed
feat: allow proto_library as rb_library#dep
1 parent c5c3044 commit 103669e

File tree

6 files changed

+126
-0
lines changed

6 files changed

+126
-0
lines changed

MODULE.bazel

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ bazel_dep(name = "platforms", version = "0.0.5")
1616
bazel_dep(name = "rules_cc", version = "0.0.9")
1717
bazel_dep(name = "rules_java", version = "7.2.0")
1818

19+
# NB: LOWER BOUND on earliest BCR release of protobuf module, to avoid upgrading the root module by accident
20+
bazel_dep(name = "protobuf", version = "3.19.6")
21+
1922
# Ruleset development dependencies.
2023
bazel_dep(name = "aspect_bazel_lib", version = "2.21.2", dev_dependency = True)
2124
bazel_dep(name = "bazel_skylib_gazelle_plugin", version = "1.8.2", dev_dependency = True)

ruby/BUILD

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ toolchain_type(
99
visibility = ["//visibility:public"],
1010
)
1111

12+
toolchain_type(
13+
name = "ruby_grpc_protoc_plugin.toolchain_type",
14+
visibility = ["//visibility:public"],
15+
)
16+
1217
bzl_library(
1318
name = "defs",
1419
srcs = ["defs.bzl"],

ruby/private/library.bzl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"Implementation details for rb_library"
22

3+
load("//ruby/private:proto.bzl", "ruby_proto_aspect")
34
load(
45
"//ruby/private:providers.bzl",
56
"BundlerInfo",
@@ -18,6 +19,8 @@ ATTRS = {
1819
),
1920
"deps": attr.label_list(
2021
doc = "List of other Ruby libraries the target depends on.",
22+
# Convert proto_library deps to provide RubyFilesInfo
23+
aspects = [ruby_proto_aspect],
2124
),
2225
"data": attr.label_list(
2326
allow_files = True,

ruby/private/proto.bzl

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
"""Ruby proto generation aspect for rules_ruby.
2+
3+
This file is loaded by `rules_ruby` (see its `rb_library` implementation) using
4+
`@@//proto:ruby.bzl`, so it must remain in this workspace at `proto/ruby.bzl`.
5+
"""
6+
7+
load("@protobuf//bazel/common:proto_info.bzl", "ProtoInfo")
8+
load("//ruby/private:providers.bzl", "GrpcPluginInfo", "RubyFilesInfo")
9+
10+
def _ruby_grpc_protoc_plugin_toolchain_impl(ctx):
11+
return [platform_common.ToolchainInfo(
12+
grpc = GrpcPluginInfo(grpc_plugin = ctx.attr.grpc_plugin.files_to_run),
13+
)]
14+
15+
ruby_grpc_protoc_plugin_toolchain = rule(
16+
implementation = _ruby_grpc_protoc_plugin_toolchain_impl,
17+
attrs = {
18+
"grpc_plugin": attr.label(
19+
mandatory = True,
20+
executable = True,
21+
cfg = "exec",
22+
),
23+
},
24+
)
25+
26+
GRPC_PLUGIN_TOOLCHAIN = "@rules_ruby//ruby:ruby_grpc_protoc_plugin.toolchain_type"
27+
PROTO_TOOLCHAIN = "@protobuf//bazel/private:proto_toolchain_type"
28+
RUBY_OUTPUT_FILE = "{proto_file_basename}_pb.rb"
29+
RUBY_SERVICE_OUTPUT_FILE = "{proto_file_basename}_services_pb.rb"
30+
31+
def _ruby_proto_aspect_impl(target, ctx):
32+
if not ProtoInfo in target:
33+
return []
34+
35+
# Use Bazel 9 feature to have a dynamic dependency graph based on file contents
36+
has_services = ctx.actions.declare_directory(target.label.name + ".has_services")
37+
ctx.actions.run_shell(
38+
# TODO: write a more robust parser maybe with tree-sitter
39+
command = "mkdir -p {out_dir}; touch {out_dir}/has_messages; grep -q 'service' {proto_files} && touch {out_dir}/services".format(
40+
out_dir = has_services.path,
41+
proto_files = " ".join([p.path for p in target[ProtoInfo].direct_sources]),
42+
),
43+
inputs = target[ProtoInfo].direct_sources,
44+
outputs = [has_services],
45+
)
46+
47+
proto_info = ctx.toolchains[PROTO_TOOLCHAIN].proto
48+
grpc_info = ctx.toolchains[GRPC_PLUGIN_TOOLCHAIN].grpc
49+
print("grpc_info: %s" % grpc_info.grpc_plugin)
50+
print("proto_info: %s" % proto_info.proto_compiler)
51+
proto_srcs = target[ProtoInfo].direct_sources
52+
proto_deps = target[ProtoInfo].transitive_sources
53+
outputs = []
54+
for src in proto_srcs:
55+
proto_file_basename = src.basename.replace(".proto", "")
56+
msg_output = ctx.actions.declare_file(RUBY_OUTPUT_FILE.format(proto_file_basename = proto_file_basename))
57+
service_output = ctx.actions.declare_file(RUBY_SERVICE_OUTPUT_FILE.format(proto_file_basename = proto_file_basename))
58+
ctx.actions.run_shell(
59+
# https://grpc.io/docs/languages/ruby/basics/#generating-client-and-server-code
60+
# grpc_tools_ruby_protoc -I ../../protos --ruby_out=../lib --grpc_out=../lib ../../protos/route_guide.proto
61+
command = "{protoc} --plugin=protoc-gen-grpc={grpc} --ruby_out={bindir} --grpc_out={bindir} {sources}".format(
62+
protoc = proto_info.proto_compiler.executable.path,
63+
sources = " ".join([p.path for p in proto_srcs]),
64+
grpc = grpc_info.grpc_plugin.executable.path,
65+
bindir = ctx.bin_dir.path,
66+
),
67+
tools = [grpc_info.grpc_plugin, proto_info.proto_compiler],
68+
inputs = depset(proto_srcs + [has_services], transitive = [proto_deps]),
69+
outputs = [msg_output, service_output],
70+
)
71+
outputs.append(msg_output)
72+
outputs.append(service_output)
73+
return [
74+
DefaultInfo(
75+
files = depset(
76+
outputs,
77+
),
78+
),
79+
RubyFilesInfo(
80+
transitive_srcs = depset(outputs),
81+
transitive_deps = depset(),
82+
transitive_data = depset(),
83+
bundle_env = {},
84+
),
85+
]
86+
87+
ruby_proto_aspect = aspect(
88+
implementation = _ruby_proto_aspect_impl,
89+
attr_aspects = ["deps"],
90+
required_providers = [ProtoInfo],
91+
toolchains = [PROTO_TOOLCHAIN, GRPC_PLUGIN_TOOLCHAIN],
92+
)

ruby/private/providers.bzl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,13 @@ GemInfo = provider(
2929
},
3030
)
3131

32+
GrpcPluginInfo = provider(
33+
doc = "Provider for the gRPC plugin to use for proto services generation.",
34+
fields = {
35+
"grpc_plugin": "The gRPC plugin executable.",
36+
},
37+
)
38+
3239
# https://bazel.build/rules/depsets
3340

3441
def get_transitive_srcs(srcs, deps):

ruby/proto.bzl

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
load("//ruby/private:proto.bzl", "GRPC_PLUGIN_TOOLCHAIN", "ruby_grpc_protoc_plugin_toolchain")
2+
3+
def rb_grpc_protoc_plugin_toolchain(name, grpc_plugin):
4+
concrete_target = name + ".concrete"
5+
6+
ruby_grpc_protoc_plugin_toolchain(
7+
name = concrete_target,
8+
grpc_plugin = grpc_plugin,
9+
)
10+
11+
native.toolchain(
12+
name = name,
13+
toolchain_type = GRPC_PLUGIN_TOOLCHAIN,
14+
visibility = ["//visibility:public"],
15+
toolchain = concrete_target,
16+
)

0 commit comments

Comments
 (0)