Implement new semantics for `option go_package`.
Full proposal: https://github.com/golang/protobuf/issues/139
Fixes #139.
diff --git a/protoc-gen-go/generator/generator.go b/protoc-gen-go/generator/generator.go
index 65adb34..6df99ea 100644
--- a/protoc-gen-go/generator/generator.go
+++ b/protoc-gen-go/generator/generator.go
@@ -264,6 +264,31 @@
// PackageName is the package name we'll use in the generated code to refer to this file.
func (d *FileDescriptor) PackageName() string { return uniquePackageOf(d.FileDescriptorProto) }
+// goPackageOption interprets the file's go_package option.
+// If there is no go_package, it returns ("", "", false).
+// If there's a simple name, it returns ("", pkg, true).
+// If the option implies an import path, it returns (impPath, pkg, true).
+func (d *FileDescriptor) goPackageOption() (impPath, pkg string, ok bool) {
+ pkg = d.GetOptions().GetGoPackage()
+ if pkg == "" {
+ return
+ }
+ ok = true
+ // The presence of a slash implies there's an import path.
+ slash := strings.LastIndexByte(pkg, '/')
+ if slash < 0 {
+ return
+ }
+ impPath, pkg = pkg, pkg[slash+1:]
+ // A semicolon-delimited suffix overrides the package name.
+ sc := strings.IndexByte(impPath, ';')
+ if sc < 0 {
+ return
+ }
+ impPath, pkg = impPath[:sc], impPath[sc+1:]
+ return
+}
+
// goPackageName returns the Go package name to use in the
// generated Go file. The result explicit reports whether the name
// came from an option go_package statement. If explicit is false,
@@ -271,10 +296,8 @@
// or the input file name.
func (d *FileDescriptor) goPackageName() (name string, explicit bool) {
// Does the file have a "go_package" option?
- if opts := d.Options; opts != nil {
- if pkg := opts.GetGoPackage(); pkg != "" {
- return pkg, true
- }
+ if _, pkg, ok := d.goPackageOption(); ok {
+ return pkg, true
}
// Does the file have a package clause?
@@ -285,6 +308,26 @@
return baseName(d.GetName()), false
}
+// goFileName returns the output name for the generated Go file.
+func (d *FileDescriptor) goFileName() string {
+ name := *d.Name
+ if ext := path.Ext(name); ext == ".proto" || ext == ".protodevel" {
+ name = name[:len(name)-len(ext)]
+ }
+ name += ".pb.go"
+
+ // Does the file have a "go_package" option?
+ // If it does, it may override the filename.
+ if impPath, _, ok := d.goPackageOption(); ok && impPath != "" {
+ // Replace the existing dirname with the declared import path.
+ _, name = path.Split(name)
+ name = path.Join(impPath, name)
+ return name
+ }
+
+ return name
+}
+
func (d *FileDescriptor) addExport(obj Object, sym symbol) {
d.exported[obj] = append(d.exported[obj], sym)
}
@@ -512,7 +555,7 @@
Param map[string]string // Command-line parameters.
PackageImportPath string // Go import path of the package we're generating code for
ImportPrefix string // String to prefix to imported package file names.
- ImportMap map[string]string // Mapping from import name to generated name
+ ImportMap map[string]string // Mapping from .proto file name to import path
Pkg map[string]string // The names under which we import support packages
@@ -1098,7 +1141,7 @@
continue
}
g.Response.File[i] = new(plugin.CodeGeneratorResponse_File)
- g.Response.File[i].Name = proto.String(goFileName(*file.Name))
+ g.Response.File[i].Name = proto.String(file.goFileName())
g.Response.File[i].Content = proto.String(g.String())
i++
}
@@ -1285,7 +1328,7 @@
if fd.PackageName() == g.packageName {
continue
}
- filename := goFileName(s)
+ filename := fd.goFileName()
// By default, import path is the dirname of the Go filename.
importPath := path.Dir(filename)
if substitution, ok := g.ImportMap[s]; ok {
@@ -2679,15 +2722,6 @@
// dottedSlice turns a sliced name into a dotted name.
func dottedSlice(elem []string) string { return strings.Join(elem, ".") }
-// Given a .proto file name, return the output name for the generated Go program.
-func goFileName(name string) string {
- ext := path.Ext(name)
- if ext == ".proto" || ext == ".protodevel" {
- name = name[0 : len(name)-len(ext)]
- }
- return name + ".pb.go"
-}
-
// Is this field optional?
func isOptional(field *descriptor.FieldDescriptorProto) bool {
return field.Label != nil && *field.Label == descriptor.FieldDescriptorProto_LABEL_OPTIONAL
diff --git a/protoc-gen-go/generator/name_test.go b/protoc-gen-go/generator/name_test.go
index f926918..a5ebc85 100644
--- a/protoc-gen-go/generator/name_test.go
+++ b/protoc-gen-go/generator/name_test.go
@@ -33,6 +33,8 @@
import (
"testing"
+
+ "github.com/golang/protobuf/protoc-gen-go/descriptor"
)
func TestCamelCase(t *testing.T) {
@@ -54,3 +56,30 @@
}
}
}
+
+func TestGoPackageOption(t *testing.T) {
+ tests := []struct {
+ in string
+ impPath, pkg string
+ ok bool
+ }{
+ {"", "", "", false},
+ {"foo", "", "foo", true},
+ {"github.com/golang/bar", "github.com/golang/bar", "bar", true},
+ {"github.com/golang/bar;baz", "github.com/golang/bar", "baz", true},
+ }
+ for _, tc := range tests {
+ d := &FileDescriptor{
+ FileDescriptorProto: &descriptor.FileDescriptorProto{
+ Options: &descriptor.FileOptions{
+ GoPackage: &tc.in,
+ },
+ },
+ }
+ impPath, pkg, ok := d.goPackageOption()
+ if impPath != tc.impPath || pkg != tc.pkg || ok != tc.ok {
+ t.Errorf("go_package = %q => (%q, %q, %t), want (%q, %q, %t)", tc.in,
+ impPath, pkg, ok, tc.impPath, tc.pkg, tc.ok)
+ }
+ }
+}