Move protobuf enums to a separate .pbenum.dart file.
This is to reduce the amount of Dart code that Dartium needs to
load when only the enums from a .proto file are needed.
Reviewed internally: cl/123342330
diff --git a/lib/bazel.dart b/lib/bazel.dart
index 6bfe90a..f66cb65 100644
--- a/lib/bazel.dart
+++ b/lib/bazel.dart
@@ -105,7 +105,7 @@
}
@override
- Uri outputPathFor(Uri input) {
+ Uri outputPathFor(Uri input, String extension) {
var pkg = _findPackage(input.path);
if (pkg == null) {
throw new ArgumentError('Unable to locate package for input $input.');
@@ -113,14 +113,15 @@
// Bazel package-relative paths.
var relativeInput = input.path.substring('${pkg.input_root}/'.length);
- var relativeOutput = replacePathExtension(relativeInput);
- var outputPath = p.join(pkg.output_root, relativeOutput);
+ var base = p.withoutExtension(relativeInput);
+ var outputPath = p.join(pkg.output_root, "$base$extension");
return new Uri.file(outputPath);
}
@override
- Uri resolveImport(Uri target, Uri source) {
- var targetUri = _packageUriFor(replacePathExtension(target.path));
+ Uri resolveImport(Uri target, Uri source, String extension) {
+ var targetBase = p.withoutExtension(target.path);
+ var targetUri = _packageUriFor("$targetBase$extension");
var sourceUri = _packageUriFor(source.path);
if (targetUri == null && sourceUri != null) {
@@ -134,7 +135,7 @@
return targetUri.uri;
}
- return super.resolveImport(target, source);
+ return super.resolveImport(target, source, extension);
}
_PackageUri _packageUriFor(String target) {
diff --git a/lib/code_generator.dart b/lib/code_generator.dart
index 2072de2..92561a4 100644
--- a/lib/code_generator.dart
+++ b/lib/code_generator.dart
@@ -66,6 +66,7 @@
var name = gen._fileDescriptor.name;
if (request.fileToGenerate.contains(name)) {
response.file.add(gen.generateResponse(config));
+ response.file.add(gen.generateEnumResponse(config));
response.file.add(gen.generateJsonDartResponse(config));
}
}
diff --git a/lib/extension_generator.dart b/lib/extension_generator.dart
index 91260aa..e675750 100644
--- a/lib/extension_generator.dart
+++ b/lib/extension_generator.dart
@@ -44,15 +44,20 @@
///
/// For each .pb.dart file that the generated code needs to import,
/// add its generator.
- void addImportsTo(Set<FileGenerator> imports) {
+ void addImportsTo(
+ Set<FileGenerator> imports, Set<FileGenerator> enumImports) {
if (_field == null) throw new StateError("resolve not called");
var typeGen = _field.baseType.generator;
if (typeGen != null && typeGen.fileGen != fileGen) {
// The type of this extension is defined in a different file,
// so we need to import it.
+ if (typeGen is EnumGenerator) {
+ enumImports.add(typeGen.fileGen);
+ } else {
imports.add(typeGen.fileGen);
}
}
+ }
/// Adds dependencies of [generateConstants] to [imports].
///
diff --git a/lib/file_generator.dart b/lib/file_generator.dart
index 76c5a7f..f1f78f2 100644
--- a/lib/file_generator.dart
+++ b/lib/file_generator.dart
@@ -118,7 +118,7 @@
Uri filePath = new Uri.file(_fileDescriptor.name);
return new CodeGeneratorResponse_File()
- ..name = config.outputPathFor(filePath).path
+ ..name = config.outputPathFor(filePath, ".pb.dart").path
..content = out.toString();
}
@@ -130,9 +130,6 @@
generateHeader(out, config);
// Generate code.
- for (EnumGenerator e in enumGenerators) {
- e.generate(out);
- }
for (MessageGenerator m in messageGenerators) {
m.generate(out);
}
@@ -181,7 +178,11 @@
if (_needsFixnumImport) {
out.println("import 'package:fixnum/fixnum.dart';");
}
+
+ if (_needsProtobufImport) {
out.println("import 'package:protobuf/protobuf.dart';");
+ out.println();
+ }
var mixinImports = findMixinsToImport();
var importNames = mixinImports.keys.toList();
@@ -190,23 +191,46 @@
var symbols = mixinImports[imp];
out.println("import '${imp}' show ${symbols.join(', ')};");
}
+ if (mixinImports.isNotEmpty) out.println();
// Import the .pb.dart files we depend on.
- for (var imported in _findProtosToImport()) {
+ var imports = new Set<FileGenerator>.identity();
+ var enumImports = new Set<FileGenerator>.identity();
+ _findProtosToImport(imports, enumImports);
+
+ void writeImport(FileGenerator target, String extension) {
Uri resolvedImport =
- config.resolveImport(imported.protoFileUri, protoFileUri);
+ config.resolveImport(target.protoFileUri, protoFileUri, extension);
out.print("import '$resolvedImport'");
- if (package != imported.package && imported.package.isNotEmpty) {
- out.print(' as ${imported.packageImportPrefix}');
+ if (package != target.package && target.package.isNotEmpty) {
+ out.print(' as ${target.packageImportPrefix}');
}
out.println(';');
}
- out.println();
+
+ for (var target in imports) {
+ writeImport(target, ".pb.dart");
+ }
+ if (imports.isNotEmpty) out.println();
+
+ for (var target in enumImports) {
+ writeImport(target, ".pbenum.dart");
+ }
+ if (enumImports.isNotEmpty) out.println();
// Services also depend on the json imports.
if (serviceGenerators.isNotEmpty) {
- Uri resolvedImport = config.resolveJsonImport(protoFileUri, protoFileUri);
- out.print("import '$resolvedImport';");
+ Uri resolvedImport =
+ config.resolveImport(protoFileUri, protoFileUri, ".pbjson.dart");
+ out.println("import '$resolvedImport';");
+ out.println();
+ }
+
+ // Export enums in main file for backward compatibility.
+ if (enumCount > 0) {
+ Uri resolvedImport =
+ config.resolveImport(protoFileUri, protoFileUri, ".pbenum.dart");
+ out.println("export '$resolvedImport';");
out.println();
}
}
@@ -221,20 +245,26 @@
return false;
}
+ bool get _needsProtobufImport =>
+ messageGenerators.isNotEmpty ||
+ extensionGenerators.isNotEmpty ||
+ clientApiGenerators.isNotEmpty ||
+ serviceGenerators.isNotEmpty;
+
/// Returns the generator for each .pb.dart file we need to import.
- Set<FileGenerator> _findProtosToImport() {
- var imports = new Set<FileGenerator>.identity();
+ void _findProtosToImport(
+ Set<FileGenerator> imports, Set<FileGenerator> enumImports) {
for (var m in messageGenerators) {
- m.addImportsTo(imports);
+ m.addImportsTo(imports, enumImports);
}
for (var x in extensionGenerators) {
- x.addImportsTo(imports);
+ x.addImportsTo(imports, enumImports);
}
for (var x in serviceGenerators) {
x.addImportsTo(imports);
}
- imports.remove(this); // Don't need to import self.
- return imports;
+ // Don't need to import self. (But we may need to import the enums.)
+ imports.remove(this);
}
/// Returns a map from import names to the Dart symbols to be imported.
@@ -262,6 +292,60 @@
return imports;
}
+ CodeGeneratorResponse_File generateEnumResponse(OutputConfiguration config) {
+ if (!_linked) throw new StateError("not linked");
+
+ IndentingWriter out = new IndentingWriter();
+
+ generateEnumFile(out, config);
+
+ Uri filePath = new Uri.file(_fileDescriptor.name);
+ return new CodeGeneratorResponse_File()
+ ..name = config.outputPathFor(filePath, ".pbenum.dart").path
+ ..content = out.toString();
+ }
+
+ void generateEnumFile(IndentingWriter out,
+ [OutputConfiguration config = const DefaultOutputConfiguration()]) {
+ Uri filePath = new Uri.file(_fileDescriptor.name);
+ if (filePath.isAbsolute) {
+ // protoc should never generate a file descriptor with an absolute path.
+ throw "FAILURE: File with an absolute path is not supported";
+ }
+
+ var baseLibraryName = _generateLibraryName(filePath);
+ var libraryName = baseLibraryName + "_pbenum";
+ out.print('''
+///
+// Generated code. Do not modify.
+///
+library $libraryName;
+
+''');
+
+ if (enumCount > 0) {
+ out.println("import 'package:protobuf/protobuf.dart';");
+ out.println();
+ }
+
+ for (EnumGenerator e in enumGenerators) {
+ e.generate(out);
+ }
+
+ for (MessageGenerator m in messageGenerators) {
+ m.generateEnums(out);
+ }
+ }
+
+ /// Returns the number of enum types generated in the .pbenum.dart file.
+ int get enumCount {
+ var count = enumGenerators.length;
+ for (MessageGenerator m in messageGenerators) {
+ count += m.enumCount;
+ }
+ return count;
+ }
+
CodeGeneratorResponse_File generateJsonDartResponse(
OutputConfiguration config) {
if (!_linked) throw new StateError("not linked");
@@ -272,7 +356,7 @@
Uri filePath = new Uri.file(_fileDescriptor.name);
return new CodeGeneratorResponse_File()
- ..name = config.jsonDartOutputPathFor(filePath).path
+ ..name = config.outputPathFor(filePath, ".pbjson.dart").path
..content = out.toString();
}
@@ -297,8 +381,8 @@
// Import the .pbjson.dart files we depend on.
var importList = _findJsonProtosToImport();
for (var imported in importList) {
- Uri resolvedImport =
- config.resolveJsonImport(imported.protoFileUri, protoFileUri);
+ Uri resolvedImport = config.resolveImport(
+ imported.protoFileUri, protoFileUri, ".pbjson.dart");
out.print("import '$resolvedImport'");
if (package != imported.package && imported.package.isNotEmpty) {
out.print(' as ${imported.packageImportPrefix}');
diff --git a/lib/message_generator.dart b/lib/message_generator.dart
index 4384d88..a3d47b6 100644
--- a/lib/message_generator.dart
+++ b/lib/message_generator.dart
@@ -197,24 +197,34 @@
///
/// For each .pb.dart file that the generated code needs to import,
/// add its generator.
- void addImportsTo(Set<FileGenerator> imports) {
+ void addImportsTo(
+ Set<FileGenerator> imports, Set<FileGenerator> enumImports) {
if (_fieldList == null) throw new StateError("message not resolved");
for (var field in _fieldList) {
var typeGen = field.baseType.generator;
- if (typeGen != null && typeGen.fileGen != fileGen) {
- // The field's type is defined in a different .pb.dart file.
- // Therefore we need to import it.
+ if (typeGen is EnumGenerator) {
+ enumImports.add(typeGen.fileGen);
+ } else if (typeGen != null) {
imports.add(typeGen.fileGen);
}
}
for (var m in _messageGenerators) {
- m.addImportsTo(imports);
+ m.addImportsTo(imports, enumImports);
}
for (var x in _extensionGenerators) {
- x.addImportsTo(imports);
+ x.addImportsTo(imports, enumImports);
}
}
+ // Returns the number of enums in this message and all nested messages.
+ int get enumCount {
+ var count = _enumGenerators.length;
+ for (var m in _messageGenerators) {
+ count += m.enumCount;
+ }
+ return count;
+ }
+
/// Adds dependencies of [generateConstants] to [imports].
///
/// For each .pbjson.dart file that the generated code needs to import,
@@ -241,10 +251,6 @@
_methodNames.addAll(mixin.findReservedNames());
}
- for (EnumGenerator e in _enumGenerators) {
- e.generate(out);
- }
-
for (MessageGenerator m in _messageGenerators) {
m.generate(out);
}
@@ -408,6 +414,16 @@
}
}
+ void generateEnums(IndentingWriter out) {
+ for (EnumGenerator e in _enumGenerators) {
+ e.generate(out);
+ }
+
+ for (MessageGenerator m in _messageGenerators) {
+ m.generateEnums(out);
+ }
+ }
+
/// Writes a Dart constant containing the JSON for the ProtoDescriptor.
/// Also writes a separate constant for each nested message,
/// to avoid duplication.
diff --git a/lib/output_config.dart b/lib/output_config.dart
index 481d03d..534f24e 100644
--- a/lib/output_config.dart
+++ b/lib/output_config.dart
@@ -9,46 +9,20 @@
abstract class OutputConfiguration {
const OutputConfiguration();
- /// Returns [filePath] with it's extension replaced with '.pb.dart'.
- String replacePathExtension(String filePath) =>
- '${path.withoutExtension(filePath)}.pb.dart';
-
- /// Returns [file] with it's extension replaced with '.pb.dart'.
- Uri replaceUriExtension(Uri file) =>
- path.url.toUri(replacePathExtension(path.url.fromUri(file)));
-
- /// Resolves an import of a .pb.dart file.
+ /// Resolves an import of a generated Dart file.
///
/// Both [source] and [target] are .proto files, where [source] imports
/// [target].
///
- /// The returned URI can be used within the source's .pb.dart file to
- /// import the target's .pb.dart file.
- Uri resolveImport(Uri target, Uri source);
-
- /// Resolves an import of a .pbjson.dart file.
- ///
- /// Both [source] and [target] are .proto files, where [source] imports
- /// [target].
- ///
- /// The returned URI can be used within the source's .pbjson.dart file to
- /// import the target's .pbjson.dart file.
- Uri resolveJsonImport(Uri target, Uri source);
+ /// The returned URI can be used within one of the source's dart files to
+ /// import the target's generated file with the given extension.
+ Uri resolveImport(Uri target, Uri source, String extension);
/// Returns the path where the .pb.dart file will be placed.
///
/// The input is a .proto file and the output is a path under the output
/// folder.
- Uri outputPathFor(Uri inputPath);
-
- /// Returns the path where the .pbjson.dart file will be place.
- ///
- /// The input is a .proto file and the output is a path under the output
- /// folder.
- ///
- /// (This file making data from the .proto file available as
- /// constants in Dart. The constants are JSON-encoded protobufs.)
- Uri jsonDartOutputPathFor(Uri inputPath);
+ Uri outputPathFor(Uri inputPath, String extension);
}
/// Default [OutputConfiguration] that uses the same path as the input
@@ -58,28 +32,17 @@
const DefaultOutputConfiguration();
@override
- Uri outputPathFor(Uri input) => replaceUriExtension(input);
-
- @override
- Uri jsonDartOutputPathFor(Uri input) {
+ Uri outputPathFor(Uri input, String extension) {
var base = path.withoutExtension(path.url.fromUri(input));
- return path.url.toUri('$base.pbjson.dart');
+ return path.url.toUri('$base$extension');
}
@override
- Uri resolveImport(Uri target, Uri source) {
- var targetPath = path.url.fromUri(target);
- var sourceDir = path.url.dirname(path.url.fromUri(source));
- return path.url.toUri(
- replacePathExtension(path.url.relative(targetPath, from: sourceDir)));
- }
-
- @override
- Uri resolveJsonImport(Uri target, Uri source) {
+ Uri resolveImport(Uri target, Uri source, String extension) {
var targetPath = path.url.fromUri(target);
var sourceDir = path.url.dirname(path.url.fromUri(source));
var base =
path.withoutExtension(path.url.relative(targetPath, from: sourceDir));
- return path.url.toUri('$base.pbjson.dart');
+ return path.url.toUri('$base$extension');
}
}
diff --git a/test/bazel_test.dart b/test/bazel_test.dart
index 7f1773d..64ed08a 100644
--- a/test/bazel_test.dart
+++ b/test/bazel_test.dart
@@ -112,7 +112,7 @@
group('BazelOutputConfiguration', () {
Map<String, BazelPackage> packages;
- var config;
+ BazelOutputConfiguration config;
setUp(() {
packages = {
@@ -126,28 +126,34 @@
group('outputPathForUri', () {
test('should handle files at package root', () {
- var p = config.outputPathFor(Uri.parse('foo/bar/quux.proto'));
+ var p =
+ config.outputPathFor(Uri.parse('foo/bar/quux.proto'), '.pb.dart');
expect(p.path, 'baz/flob/quux.pb.dart');
});
test('should handle files below package root', () {
- var p = config.outputPathFor(Uri.parse('foo/bar/a/b/quux.proto'));
+ var p = config.outputPathFor(
+ Uri.parse('foo/bar/a/b/quux.proto'), '.pb.dart');
expect(p.path, 'baz/flob/a/b/quux.pb.dart');
});
test('should handle files in a nested package root', () {
- var p = config.outputPathFor(Uri.parse('foo/bar/baz/quux.proto'));
+ var p = config.outputPathFor(
+ Uri.parse('foo/bar/baz/quux.proto'), '.pb.dart');
expect(p.path, 'baz/flob/foo/quux.pb.dart');
});
test('should handle files below a nested package root', () {
- var p = config.outputPathFor(Uri.parse('foo/bar/baz/a/b/quux.proto'));
+ var p = config.outputPathFor(
+ Uri.parse('foo/bar/baz/a/b/quux.proto'), '.pb.dart');
expect(p.path, 'baz/flob/foo/a/b/quux.pb.dart');
});
test('should throw if unable to locate the package for an input', () {
expect(
- () => config.outputPathFor(Uri.parse('a/b/c/quux.proto')), throws);
+ () =>
+ config.outputPathFor(Uri.parse('a/b/c/quux.proto'), '.pb.dart'),
+ throws);
});
});
@@ -155,28 +161,28 @@
test('should emit relative import if in same package', () {
var target = Uri.parse('foo/bar/quux.proto');
var source = Uri.parse('foo/bar/baz.proto');
- var uri = config.resolveImport(target, source);
+ var uri = config.resolveImport(target, source, '.pb.dart');
expect(uri.path, 'quux.pb.dart');
});
test('should emit relative import if in subdir of same package', () {
var target = Uri.parse('foo/bar/a/b/quux.proto');
var source = Uri.parse('foo/bar/baz.proto');
- var uri = config.resolveImport(target, source);
+ var uri = config.resolveImport(target, source, '.pb.dart');
expect(uri.path, 'a/b/quux.pb.dart');
});
test('should emit relative import if in parent dir in same package', () {
var target = Uri.parse('foo/bar/quux.proto');
var source = Uri.parse('foo/bar/a/b/baz.proto');
- var uri = config.resolveImport(target, source);
+ var uri = config.resolveImport(target, source, '.pb.dart');
expect(uri.path, '../../quux.pb.dart');
});
test('should emit package: import if in different package', () {
var target = Uri.parse('wibble/wobble/quux.proto');
var source = Uri.parse('foo/bar/baz.proto');
- var uri = config.resolveImport(target, source);
+ var uri = config.resolveImport(target, source, '.pb.dart');
expect(uri.scheme, 'package');
expect(uri.path, 'wibble.wobble/quux.pb.dart');
});
@@ -184,7 +190,7 @@
test('should emit package: import if in subdir of different package', () {
var target = Uri.parse('wibble/wobble/foo/bar/quux.proto');
var source = Uri.parse('foo/bar/baz.proto');
- var uri = config.resolveImport(target, source);
+ var uri = config.resolveImport(target, source, '.pb.dart');
expect(uri.scheme, 'package');
expect(uri.path, 'wibble.wobble/foo/bar/quux.pb.dart');
});
@@ -192,7 +198,7 @@
test('should throw if target is in unknown package', () {
var target = Uri.parse('flob/flub/quux.proto');
var source = Uri.parse('foo/bar/baz.proto');
- expect(() => config.resolveImport(target, source), throws);
+ expect(() => config.resolveImport(target, source, '.pb.dart'), throws);
});
});
});
diff --git a/test/file_generator_test.dart b/test/file_generator_test.dart
index a8c98e4..a05a12f 100644
--- a/test/file_generator_test.dart
+++ b/test/file_generator_test.dart
@@ -162,14 +162,25 @@
expect(writer.toString(), expected);
});
- test('FileGenerator generates a .pb.dart file for a top-level enum', () {
+ test('FileGenerator generates files for a top-level enum', () {
// NOTE: Below > 80 cols because it is matching generated code > 80 cols.
+
String expected = r'''
///
// Generated code. Do not modify.
///
library test;
+export 'test.pbenum.dart';
+
+''';
+
+ String expectedEnum = r'''
+///
+// Generated code. Do not modify.
+///
+library test_pbenum;
+
import 'package:protobuf/protobuf.dart';
class PhoneType extends ProtobufEnum {
@@ -195,6 +206,7 @@
}
''';
+
FileDescriptorProto fd =
buildFileDescriptor(phoneNumber: false, topLevelEnum: true);
var options = parseGenerationOptions(
@@ -206,6 +218,10 @@
var writer = new IndentingWriter();
fg.generate(writer);
expect(writer.toString(), expected);
+
+ writer = new IndentingWriter();
+ fg.generateEnumFile(writer);
+ expect(writer.toString(), expectedEnum);
});
test('FileGenerator generates a .pbjson.dart file for a top-level enum', () {
@@ -308,6 +324,7 @@
import 'package:protobuf/protobuf.dart';
import 'test.pbjson.dart';
+
''';
FileDescriptorProto fd = new FileDescriptorProto()
..name = 'test'
@@ -400,6 +417,7 @@
library test;
import 'package:protobuf/protobuf.dart';
+
import 'package1.pb.dart' as p1;
import 'package2.pb.dart' as p2;
diff --git a/test/message_generator_test.dart b/test/message_generator_test.dart
index 7b7eaa4..9cb0a33 100755
--- a/test/message_generator_test.dart
+++ b/test/message_generator_test.dart
@@ -14,7 +14,7 @@
void main() {
test('testMessageGenerator', () {
// NOTE: Below > 80 cols because it is matching generated code > 80 cols.
- String expected = r'''
+ String expectedEnums = r'''
class PhoneNumber_PhoneType extends ProtobufEnum {
static const PhoneNumber_PhoneType MOBILE = const PhoneNumber_PhoneType._(0, 'MOBILE');
static const PhoneNumber_PhoneType HOME = const PhoneNumber_PhoneType._(1, 'HOME');
@@ -37,6 +37,9 @@
const PhoneNumber_PhoneType._(int v, String n) : super(v, n);
}
+''';
+
+ String expected = r'''
class PhoneNumber extends GeneratedMessage {
static final BuilderInfo _i = new BuilderInfo('PhoneNumber')
..a/*<String>*/(1, 'number', PbFieldType.QS)
@@ -133,5 +136,9 @@
var writer = new IndentingWriter();
mg.generate(writer);
expect(writer.toString(), expected);
+
+ writer = new IndentingWriter();
+ mg.generateEnums(writer);
+ expect(writer.toString(), expectedEnums);
});
}