Skip to content
This repository has been archived by the owner on Jan 26, 2021. It is now read-only.

Commit

Permalink
gRPC stub generation. (#79)
Browse files Browse the repository at this point in the history
Added support for generating gRPC stubs. gRPC mode is selected by adding
the option 'grpc' to the `--dart_out` argument, as in
`--dart_out=grpc:<path>`.

When gRPC mode is selected, the legacy ("generic") RPC stubs will not be
emitted.
  • Loading branch information
jakobr-google authored Jul 5, 2017
1 parent 2467861 commit f2a9c14
Show file tree
Hide file tree
Showing 12 changed files with 611 additions and 34 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
## Unreleased

### gRPC support

* Added gRPC stub generation.
* Updated descriptor.proto from google/protobuf v3.3.0.

## 0.7.2 - 2017-06-12

* Added CHANGELOG.md
Expand Down
2 changes: 1 addition & 1 deletion lib/code_generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ class CodeGenerator extends ProtobufContainer {
// (We may import it even if we don't generate the .pb.dart file.)
List<FileGenerator> generators = <FileGenerator>[];
for (FileDescriptorProto file in request.protoFile) {
generators.add(new FileGenerator(file));
generators.add(new FileGenerator(file, options));
}

// Collect field types and importable files.
Expand Down
73 changes: 60 additions & 13 deletions lib/file_generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
part of protoc;

final _dartIdentifier = new RegExp(r'^\w+$');
final _formatter = new DartFormatter();

/// Generates the Dart output files for one .proto input file.
///
Expand Down Expand Up @@ -90,22 +91,23 @@ class FileGenerator extends ProtobufContainer {
}

final FileDescriptorProto descriptor;
final GenerationOptions options;

// The relative path used to import the .proto file, as a URI.
final Uri protoFileUri;

final List<EnumGenerator> enumGenerators = <EnumGenerator>[];
final List<MessageGenerator> messageGenerators = <MessageGenerator>[];
final List<ExtensionGenerator> extensionGenerators = <ExtensionGenerator>[];
final List<ClientApiGenerator> clientApiGenerators = <ClientApiGenerator>[];
final List<ServiceGenerator> serviceGenerators = <ServiceGenerator>[];
final enumGenerators = <EnumGenerator>[];
final messageGenerators = <MessageGenerator>[];
final extensionGenerators = <ExtensionGenerator>[];
final clientApiGenerators = <ClientApiGenerator>[];
final serviceGenerators = <ServiceGenerator>[];
final grpcGenerators = <GrpcServiceGenerator>[];

/// True if cross-references have been resolved.
bool _linked = false;

FileGenerator(FileDescriptorProto descriptor)
: descriptor = descriptor,
protoFileUri = new Uri.file(descriptor.name) {
FileGenerator(this.descriptor, this.options)
: protoFileUri = new Uri.file(descriptor.name) {
if (protoFileUri.isAbsolute) {
// protoc should never generate an import with an absolute path.
throw "FAILURE: Import with absolute path is not supported";
Expand Down Expand Up @@ -133,9 +135,13 @@ class FileGenerator extends ProtobufContainer {
extensionGenerators.add(new ExtensionGenerator(extension, this));
}
for (ServiceDescriptorProto service in descriptor.service) {
var serviceGen = new ServiceGenerator(service, this);
serviceGenerators.add(serviceGen);
clientApiGenerators.add(new ClientApiGenerator(serviceGen));
if (options.useGrpc) {

This comment has been minimized.

Copy link
@skybrian

skybrian Jul 5, 2017

Contributor

@jakobr-google: It seems unfortunate that it only generates one or the other, because it will make using gRPC with Bazel harder. When the Bazel build rules run protoc using Bazel and aspects, the aspect doesn't know what the downstream dependencies will need, and they could have conflicting requirements, so using a flag seems like a problem?

A more Bazel-friendly alternative would be to generate both files all the time.

This comment has been minimized.

Copy link
@jakobr-google

jakobr-google Jul 6, 2017

Author Contributor

Generating both is also problematic, since the legacy RPC generator adds a XxApi stub in xx.pb.dart, which will always get imported and confuse things. And having the .pbserver.dart around with a XxServiceBase conflicting with .pbgrpc.dart's XxServiceBase is also just looking for trouble.

I assumed we could have a 'useGrpc' flag on a dart_proto_library rule, or something like that?

In general, I'd like to avoid generating unnecesary files, so I'm hoping to eventually only generate .pbenum.dart files if there are enums, and maybe also only generate .pbjson.dart if using generic RPC (or if specifically requested by a flag).

This comment has been minimized.

Copy link
@skybrian

skybrian Jul 6, 2017

Contributor

Renaming one of the ServiceBase suffixes seems like a good idea to avoid confusing them. GrpcBase perhaps?

There's a question of what happens when one proto_library rule depends on another. The upstream rule wouldn't know about the downstream rule's flag, so it needs to generate anything the downstream rule might need. But the downstream rule doesn't need to import it.

I don't think this is an issue for services since the symbols aren't used downstream, but it is an issue for the JSON file which could be used by any downstream rule that has a service.

Regarding omitting files, a Bazel rule declares input and output files in ahead of time (without reading proto files) so it would probably need to generate an empty file anyway.

On the other hand, the reason we have separate enum files at all was to make Dartium faster so maybe that won't be needed anymore?

grpcGenerators.add(new GrpcServiceGenerator(service, this));
} else {
var serviceGen = new ServiceGenerator(service, this);
serviceGenerators.add(serviceGen);
clientApiGenerators.add(new ClientApiGenerator(serviceGen));
}
}
}

Expand Down Expand Up @@ -178,12 +184,19 @@ class FileGenerator extends ProtobufContainer {
..content = content;
}

return [
final files = [
makeFile(".pb.dart", generateMainFile(config)),
makeFile(".pbenum.dart", generateEnumFile(config)),
makeFile(".pbserver.dart", generateServerFile(config)),
makeFile(".pbjson.dart", generateJsonFile(config)),
];
if (options.useGrpc) {
if (grpcGenerators.isNotEmpty) {
files.add(makeFile(".pbgrpc.dart", generateGrpcFile(config)));
}
} else {
files.add(makeFile(".pbserver.dart", generateServerFile(config)));
}
return files;
}

/// Returns the contents of the .pb.dart file for this .proto file.
Expand Down Expand Up @@ -417,6 +430,40 @@ import 'package:protobuf/protobuf.dart';
return out.toString();
}

/// Returns the contents of the .pbgrpc.dart file for this .proto file.
String generateGrpcFile(
[OutputConfiguration config = const DefaultOutputConfiguration()]) {
if (!_linked) throw new StateError("not linked");
var out = new IndentingWriter();
_writeLibraryHeading(out, "pbgrpc");

out.println('''
import 'dart:async';
import 'package:grpc/grpc.dart';
''');

// Import .pb.dart files needed for requests and responses.
var imports = new Set<FileGenerator>();
for (var generator in grpcGenerators) {
generator.addImportsTo(imports);
}
for (var target in imports) {
_writeImport(out, config, target, ".pb.dart");
}

var resolvedImport =
config.resolveImport(protoFileUri, protoFileUri, ".pb.dart");
out.println("export '$resolvedImport';");
out.println();

for (var generator in grpcGenerators) {
generator.generate(out);
}

return _formatter.format(out.toString());
}

/// Returns the contents of the .pbjson.dart file for this .proto file.
String generateJsonFile(
[OutputConfiguration config = const DefaultOutputConfiguration()]) {
Expand Down
Loading

0 comments on commit f2a9c14

Please sign in to comment.