Skip to content

Commit 66d89df

Browse files
HoneyryderChuckcopybara-github
authored andcommitted
ruby codegen: support generation of rbs files (#15633)
this introduces support for a new protoc option, `--rbs_out`, which points to a directory where ruby type definition files, defined in the RBS format, are stored. [rbs](https://github.com/ruby/rbs) is the type signature syntax blessed by the ruby core team, used by static analysis tools such as [steep](https://github.com/soutaro/steep), which integrates with VS Code, the `irb` console (for features such as autocompletion). and [typeprof](https://github.com/ruby/typeprof). It relies on type definitions written into `.rbs` files. The `protobuf` library already exposes type definitions in [gem_rbs_collection](https://github.com/ruby/gem_rbs_collection/tree/main/gems/google-protobuf/3.22), which is used to source type definitions for libraries which do not want, or can't maintain type definitions themselves. (`protobuf` could arguably import these into this repository, lmk if you're interested). This should fix gaps such as better IDE integration, such as the ones described [here](#9495). The plan is to do roughly the same type of integration as was done for `.pyi` annotations, which also write to separate files: add the cli option and the rbs generator. protobuf classes in ruby rely on dynamic attribution of the base class, which makes what I'm trying to achieve a bit difficult. Ideally the type hierarchy could be specified statically in the ruby source code. ```ruby class Bar < AbstractMessage class Bar < Google::Protobuf::DescriptorPool.generated_pool.lookup("Bar").msgclass ``` ``` Closes #15633 FUTURE_COPYBARA_INTEGRATE_REVIEW=#15633 from HoneyryderChuck:ruby-rbs-integration 569f032 PiperOrigin-RevId: 820790992
1 parent eb083c4 commit 66d89df

15 files changed

Lines changed: 2147 additions & 5 deletions

.github/workflows/test_ruby.yml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,3 +213,23 @@ jobs:
213213
bazel-bin/protoc --proto_path=src --proto_path=ruby/tests --proto_path=ruby --ruby_out=ruby tests/basic_test.proto;
214214
${{ matrix.ffi == 'FFI' && 'PROTOCOL_BUFFERS_RUBY_IMPLEMENTATION=FFI' || '' }} ruby ruby/tests/basic.rb;
215215
${{ matrix.ffi == 'FFI' && 'PROTOCOL_BUFFERS_RUBY_IMPLEMENTATION=FFI' || '' }} ruby ruby/tests/implementation.rb
216+
217+
typecheck:
218+
name: Typecheck
219+
runs-on: ubuntu-latest
220+
steps:
221+
- name: Checkout pending changes
222+
uses: protocolbuffers/protobuf-ci/checkout@v2
223+
with:
224+
ref: ${{ inputs.safe-checkout }}
225+
- name: Run type checker
226+
uses: protocolbuffers/protobuf-ci/bazel-docker@v2
227+
with:
228+
image: us-docker.pkg.dev/protobuf-build/containers/test/linux/ruby:ruby-3.3.0-6.3.0-904cad5249547845454998ca3837a34c71fabf96
229+
credentials: ${{ secrets.GAR_SERVICE_ACCOUNT }}
230+
bazel-cache: ruby_typecheck
231+
bash:
232+
bazel build //:protoc
233+
cd ruby
234+
cp typecheck.gemfile.lock Gemfile.lock
235+
bundle exec rake steep

protobuf.bzl

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@ def _PyOuts(srcs, use_grpc_plugin = False):
6565
def _RubyOuts(srcs):
6666
return [s[:-len(".proto")] + "_pb.rb" for s in srcs]
6767

68+
def _RBSOuts(srcs):
69+
return [s[:-len(".proto")] + "_pb.rbs" for s in srcs]
70+
6871
def _CsharpOuts(srcs):
6972
return [
7073
"".join([token.capitalize() for token in src[:-len(".proto")].split("_")]) + ".cs"
@@ -158,6 +161,8 @@ def _proto_gen_impl(ctx):
158161
outs.extend(_PyOuts([src.basename], use_grpc_plugin = use_grpc_plugin))
159162
elif lang == "ruby":
160163
outs.extend(_RubyOuts([src.basename]))
164+
elif lang == "rbs":
165+
outs.extend(_RBSOuts([src.basename]))
161166

162167
# Otherwise, rely on user-supplied outs.
163168
args.append(("--%s_out=" + path_tpl) % (lang, gen_dir))
@@ -526,8 +531,6 @@ def internal_ruby_proto_library(
526531
527532
"""
528533

529-
# Note: we need to run the protoc build twice to get separate targets for
530-
# the generated header and the source files.
531534
_proto_gen(
532535
name = name + "_genproto",
533536
srcs = srcs,
@@ -552,6 +555,58 @@ def internal_ruby_proto_library(
552555
**kwargs
553556
)
554557

558+
def internal_rbs_proto_library(
559+
name,
560+
ruby_library,
561+
srcs = [],
562+
deps = [],
563+
includes = ["."],
564+
protoc = "@com_google_protobuf//:protoc",
565+
testonly = None,
566+
visibility = ["//visibility:public"],
567+
**kwargs):
568+
"""Bazel rule to create an RBS type definitions for Ruby from proto source files
569+
570+
NOTE: the rule is only an internal workaround to generate protos. The
571+
interface may change and the rule may be removed when bazel has introduced
572+
the native rule.
573+
574+
Args:
575+
name: the name of the ruby_proto_library.
576+
ruby_library: the ruby library rules to use.
577+
srcs: the .proto files to compile.
578+
deps: a list of dependency labels; must be a internal_rbs_proto_library.
579+
includes: a string indicating the include path of the .proto files.
580+
protoc: the label of the protocol compiler to generate the sources.
581+
testonly: common rule attribute (see:
582+
https://bazel.build/reference/be/common-definitions#common-attributes)
583+
visibility: the visibility of the generated files.
584+
**kwargs: other keyword arguments that are passed to ruby_library.
585+
586+
"""
587+
588+
_proto_gen(
589+
name = name + "_genproto_rbs",
590+
srcs = srcs,
591+
deps = [s + "_genproto_rbs" for s in deps],
592+
langs = ["rbs"],
593+
includes = includes,
594+
protoc = protoc,
595+
testonly = testonly,
596+
visibility = visibility,
597+
tags = ["manual"],
598+
)
599+
600+
ruby_library(
601+
name = name,
602+
srcs = [name + "_genproto_rbs"],
603+
deps = [],
604+
testonly = testonly,
605+
visibility = visibility,
606+
includes = includes,
607+
**kwargs
608+
)
609+
555610
# When canonical labels are in use, use additional "@" prefix
556611
_canonical_label_prefix = "@" if str(Label("//:protoc")).startswith("@@") else ""
557612

src/file_lists.cmake

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,7 @@ set(libprotoc_srcs
472472
${protobuf_SOURCE_DIR}/src/google/protobuf/compiler/python/helpers.cc
473473
${protobuf_SOURCE_DIR}/src/google/protobuf/compiler/python/pyi_generator.cc
474474
${protobuf_SOURCE_DIR}/src/google/protobuf/compiler/retention.cc
475+
${protobuf_SOURCE_DIR}/src/google/protobuf/compiler/ruby/rbs_generator.cc
475476
${protobuf_SOURCE_DIR}/src/google/protobuf/compiler/ruby/ruby_generator.cc
476477
${protobuf_SOURCE_DIR}/src/google/protobuf/compiler/rust/accessors/accessor_case.cc
477478
${protobuf_SOURCE_DIR}/src/google/protobuf/compiler/rust/accessors/accessors.cc
@@ -619,6 +620,7 @@ set(libprotoc_hdrs
619620
${protobuf_SOURCE_DIR}/src/google/protobuf/compiler/python/helpers.h
620621
${protobuf_SOURCE_DIR}/src/google/protobuf/compiler/python/pyi_generator.h
621622
${protobuf_SOURCE_DIR}/src/google/protobuf/compiler/retention.h
623+
${protobuf_SOURCE_DIR}/src/google/protobuf/compiler/ruby/rbs_generator.h
622624
${protobuf_SOURCE_DIR}/src/google/protobuf/compiler/ruby/ruby_generator.h
623625
${protobuf_SOURCE_DIR}/src/google/protobuf/compiler/rust/accessors/accessor_case.h
624626
${protobuf_SOURCE_DIR}/src/google/protobuf/compiler/rust/accessors/accessors.h
@@ -1395,6 +1397,7 @@ set(compiler_test_files
13951397
${protobuf_SOURCE_DIR}/src/google/protobuf/compiler/php/generator_unittest.cc
13961398
${protobuf_SOURCE_DIR}/src/google/protobuf/compiler/python/plugin_unittest.cc
13971399
${protobuf_SOURCE_DIR}/src/google/protobuf/compiler/retention_unittest.cc
1400+
${protobuf_SOURCE_DIR}/src/google/protobuf/compiler/ruby/rbs_generator_unittest.cc
13981401
${protobuf_SOURCE_DIR}/src/google/protobuf/compiler/ruby/ruby_generator_unittest.cc
13991402
${protobuf_SOURCE_DIR}/src/google/protobuf/compiler/versions_test.cc
14001403
)

src/google/protobuf/compiler/main.cc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include "google/protobuf/compiler/php/php_generator.h"
1616
#include "google/protobuf/compiler/python/generator.h"
1717
#include "google/protobuf/compiler/python/pyi_generator.h"
18+
#include "google/protobuf/compiler/ruby/rbs_generator.h"
1819
#include "google/protobuf/compiler/ruby/ruby_generator.h"
1920
#include "google/protobuf/compiler/rust/generator.h"
2021
#ifdef GOOGLE_PROTOBUF_RUNTIME_INCLUDE_BASE
@@ -95,6 +96,10 @@ int ProtobufMain(int argc, char* argv[]) {
9596
cli.RegisterGenerator("--ruby_out", "--ruby_opt", &rb_generator,
9697
"Generate Ruby source file.");
9798

99+
ruby::RBSGenerator rbs_generator;
100+
cli.RegisterGenerator("--rbs_out", "--rbs_opt", &rbs_generator,
101+
"Generate RBS type definition.");
102+
98103
// CSharp
99104
csharp::Generator csharp_generator;
100105
cli.RegisterGenerator("--csharp_out", "--csharp_opt", &csharp_generator,

src/google/protobuf/compiler/ruby/BUILD.bazel

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,14 @@ cc_library(
2020

2121
cc_library(
2222
name = "ruby",
23-
srcs = ["ruby_generator.cc"],
24-
hdrs = ["ruby_generator.h"],
23+
srcs = [
24+
"rbs_generator.cc",
25+
"ruby_generator.cc",
26+
],
27+
hdrs = [
28+
"rbs_generator.h",
29+
"ruby_generator.h",
30+
],
2531
copts = COPTS,
2632
strip_include_prefix = "/src",
2733
visibility = [
@@ -35,6 +41,8 @@ cc_library(
3541
"//src/google/protobuf/compiler:retention",
3642
"//src/google/protobuf/io",
3743
"//src/google/protobuf/io:printer",
44+
"@abseil-cpp//absl/base:check",
45+
"@abseil-cpp//absl/container:flat_hash_map",
3846
"@abseil-cpp//absl/container:flat_hash_set",
3947
"@abseil-cpp//absl/log:absl_log",
4048
"@abseil-cpp//absl/strings",
@@ -43,19 +51,27 @@ cc_library(
4351

4452
cc_test(
4553
name = "generator_unittest",
46-
srcs = ["ruby_generator_unittest.cc"],
54+
srcs = [
55+
"rbs_generator_unittest.cc",
56+
"ruby_generator_unittest.cc",
57+
],
4758
data = [
4859
"ruby_generated_code.proto",
4960
"ruby_generated_code_pb.rb",
61+
"ruby_generated_code_pb.rbs",
5062
"ruby_generated_code_proto2.proto",
5163
"ruby_generated_code_proto2_import.proto",
5264
"ruby_generated_code_proto2_pb.rb",
65+
"ruby_generated_code_proto2_pb.rbs",
5366
"ruby_generated_pkg_explicit.proto",
5467
"ruby_generated_pkg_explicit_legacy.proto",
5568
"ruby_generated_pkg_explicit_legacy_pb.rb",
69+
"ruby_generated_pkg_explicit_legacy_pb.rbs",
5670
"ruby_generated_pkg_explicit_pb.rb",
71+
"ruby_generated_pkg_explicit_pb.rbs",
5772
"ruby_generated_pkg_implicit.proto",
5873
"ruby_generated_pkg_implicit_pb.rb",
74+
"ruby_generated_pkg_implicit_pb.rbs",
5975
"//src/google/protobuf:testdata",
6076
],
6177
deps = [

0 commit comments

Comments
 (0)