Skip to content

Commit

Permalink
Implement new semantics for option go_package.
Browse files Browse the repository at this point in the history
Full proposal: #139

Fixes #139.
  • Loading branch information
dsymonds committed Mar 3, 2016
1 parent 1cc4d6f commit c75fbf0
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 16 deletions.
66 changes: 50 additions & 16 deletions protoc-gen-go/generator/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -264,17 +264,40 @@ type FileDescriptor struct {
// 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,
// the name was derived from the protocol buffer's package statement
// 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?
Expand All @@ -285,6 +308,26 @@ func (d *FileDescriptor) goPackageName() (name string, explicit bool) {
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)
}
Expand Down Expand Up @@ -512,7 +555,7 @@ type Generator struct {
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

Expand Down Expand Up @@ -1098,7 +1141,7 @@ func (g *Generator) GenerateAllFiles() {
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++
}
Expand Down Expand Up @@ -1285,7 +1328,7 @@ func (g *Generator) generateImports() {
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 {
Expand Down Expand Up @@ -2679,15 +2722,6 @@ func CamelCaseSlice(elem []string) string { return CamelCase(strings.Join(elem,
// 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
Expand Down
29 changes: 29 additions & 0 deletions protoc-gen-go/generator/name_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ package generator

import (
"testing"

"github.com/golang/protobuf/protoc-gen-go/descriptor"
)

func TestCamelCase(t *testing.T) {
Expand All @@ -54,3 +56,30 @@ func TestCamelCase(t *testing.T) {
}
}
}

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

0 comments on commit c75fbf0

Please sign in to comment.