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) + } + } +}