protoc-gen-go: add paths=source_relative option (#544)
When --go_out=paths=source_relative:., output filenames are always
derived from the input filenames. For example, the output file
for a/b/c.proto will always be a/b/c.pb.go, regardless of the
presence or absence of a go_package option in the input file.
Fixes #515.
diff --git a/README.md b/README.md
index 1b09ad7..01b29da 100644
--- a/README.md
+++ b/README.md
@@ -56,13 +56,49 @@
The generated files will be suffixed .pb.go. See the Test code below
for an example using such a file.
+## Packages and input paths ##
+
+The protocol buffer language has a concept of "packages" which does not
+correspond well to the Go notion of packages. In generated Go code,
+each source `.proto` file is associated with a single Go package. The
+name and import path for this package is specified with the `go_package`
+proto option:
+
+ option go_package = "github.com/golang/protobuf/ptypes/any";
+
+The protocol buffer compiler will attempt to derive a package name and
+import path if a `go_package` option is not present, but it is
+best to always specify one explicitly.
+
+There is a one-to-one relationship between source `.proto` files and
+generated `.pb.go` files, but any number of `.pb.go` files may be
+contained in the same Go package.
+
+The output name of a generated file is produced by replacing the
+`.proto` suffix with `.pb.go` (e.g., `foo.proto` produces `foo.pb.go`).
+However, the output directory is selected in one of two ways. Let
+us say we have `inputs/x.proto` with a `go_package` option of
+`github.com/golang/protobuf/p`. The corresponding output file may
+be:
+
+- Relative to the import path:
+
+ protoc --go_out=. inputs/x.proto
+ # writes ./github.com/golang/protobuf/p/x.pb.go
+
+ (This can work well with `--go_out=$GOPATH`.)
+
+- Relative to the input file:
+
+ protoc --go_out=paths=source_relative:. inputs/x.proto
+ # generate ./inputs/x.pb.go
+
+## Generated code ##
The package comment for the proto library contains text describing
the interface provided in Go for protocol buffers. Here is an edited
version.
-==========
-
The proto package converts data structures to and from the
wire format of protocol buffers. It works in concert with the
Go source code generated for .proto files by the protocol compiler.
@@ -170,22 +206,25 @@
To pass extra parameters to the plugin, use a comma-separated
parameter list separated from the output directory by a colon:
-
protoc --go_out=plugins=grpc,import_path=mypackage:. *.proto
-
-- `import_prefix=xxx` - a prefix that is added onto the beginning of
- all imports. Useful for things like generating protos in a
- subdirectory, or regenerating vendored protobufs in-place.
-- `import_path=foo/bar` - used as the package if no input files
- declare `go_package`. If it contains slashes, everything up to the
- rightmost slash is ignored.
+- `paths=(import | source_relative)` - specifies how the paths of
+ generated files are structured. See the "Packages and imports paths"
+ section above. The default is `import`.
- `plugins=plugin1+plugin2` - specifies the list of sub-plugins to
load. The only plugin in this repo is `grpc`.
- `Mfoo/bar.proto=quux/shme` - declares that foo/bar.proto is
associated with Go package quux/shme. This is subject to the
import_prefix parameter.
+The following parameters are deprecated and should not be used:
+
+- `import_prefix=xxx` - a prefix that is added onto the beginning of
+ all imports.
+- `import_path=foo/bar` - used as the package if no input files
+ declare `go_package`. If it contains slashes, everything up to the
+ rightmost slash is ignored.
+
## gRPC Support ##
If a proto file specifies RPC services, protoc-gen-go can be instructed to
diff --git a/protoc-gen-go/generator/generator.go b/protoc-gen-go/generator/generator.go
index 89c1cde..f943322 100644
--- a/protoc-gen-go/generator/generator.go
+++ b/protoc-gen-go/generator/generator.go
@@ -324,13 +324,17 @@
}
// goFileName returns the output name for the generated Go file.
-func (d *FileDescriptor) goFileName() string {
+func (d *FileDescriptor) goFileName(pathType pathType) string {
name := *d.Name
if ext := path.Ext(name); ext == ".proto" || ext == ".protodevel" {
name = name[:len(name)-len(ext)]
}
name += ".pb.go"
+ if pathType == pathTypeSourceRelative {
+ return name
+ }
+
// Does the file have a "go_package" option?
// If it does, it may override the filename.
if impPath, _, ok := d.goPackageOption(); ok && impPath != "" {
@@ -574,11 +578,19 @@
typeNameToObject map[string]Object // Key is a fully-qualified name in input syntax.
init []string // Lines to emit in the init function.
indent string
+ pathType pathType // How to generate output filenames.
writeOutput bool
annotateCode bool // whether to store annotations
annotations []*descriptor.GeneratedCodeInfo_Annotation // annotations to store
}
+type pathType int
+
+const (
+ pathTypeImport pathType = iota
+ pathTypeSourceRelative
+)
+
// New creates a new generator and allocates the request and response protobufs.
func New() *Generator {
g := new(Generator)
@@ -623,6 +635,15 @@
g.ImportPrefix = GoImportPath(v)
case "import_path":
g.PackageImportPath = v
+ case "paths":
+ switch v {
+ case "import":
+ g.pathType = pathTypeImport
+ case "source_relative":
+ g.pathType = pathTypeSourceRelative
+ default:
+ g.Fail(fmt.Sprintf(`Unknown path type %q: want "import" or "source_relative".`, v))
+ }
case "plugins":
pluginList = v
case "annotate_code":
@@ -1206,7 +1227,7 @@
if !g.writeOutput {
continue
}
- fname := file.goFileName()
+ fname := file.goFileName(g.pathType)
g.Response.File = append(g.Response.File, &plugin.CodeGeneratorResponse_File{
Name: proto.String(fname),
Content: proto.String(g.String()),
@@ -1215,7 +1236,7 @@
// Store the generated code annotations in text, as the protoc plugin protocol requires that
// strings contain valid UTF-8.
g.Response.File = append(g.Response.File, &plugin.CodeGeneratorResponse_File{
- Name: proto.String(file.goFileName() + ".meta"),
+ Name: proto.String(file.goFileName(g.pathType) + ".meta"),
Content: proto.String(proto.CompactTextString(&descriptor.GeneratedCodeInfo{Annotation: g.annotations})),
})
}
diff --git a/protoc-gen-go/golden_test.go b/protoc-gen-go/golden_test.go
index 77039bd..7c30017 100644
--- a/protoc-gen-go/golden_test.go
+++ b/protoc-gen-go/golden_test.go
@@ -51,13 +51,12 @@
// Compile each package, using this binary as protoc-gen-go.
for _, sources := range packages {
- args := []string{"-Itestdata", "--go_out=plugins=grpc:" + workdir}
+ args := []string{"-Itestdata", "--go_out=plugins=grpc,paths=source_relative:" + workdir}
args = append(args, sources...)
protoc(t, args)
}
// Compare each generated file to the golden version.
- relRoot := filepath.Join(workdir, "github.com/golang/protobuf/protoc-gen-go/testdata")
filepath.Walk(workdir, func(genPath string, info os.FileInfo, _ error) error {
if info.IsDir() {
return nil
@@ -65,13 +64,13 @@
// For each generated file, figure out the path to the corresponding
// golden file in the testdata directory.
- relPath, err := filepath.Rel(relRoot, genPath)
+ relPath, err := filepath.Rel(workdir, genPath)
if err != nil {
- t.Errorf("filepath.Rel(%q, %q): %v", relRoot, genPath, err)
+ t.Errorf("filepath.Rel(%q, %q): %v", workdir, genPath, err)
return nil
}
if filepath.SplitList(relPath)[0] == ".." {
- t.Errorf("generated file %q is not relative to %q", genPath, relRoot)
+ t.Errorf("generated file %q is not relative to %q", genPath, workdir)
}
goldenPath := filepath.Join("testdata", relPath)
@@ -180,6 +179,19 @@
// import_prefix applies after M.
"prefixpackage/gamma": true,
},
+ }, {
+ parameters: "paths=source_relative",
+ wantFiles: map[string]bool{
+ "alpha/a.pb.go": true,
+ "beta/b.pb.go": true,
+ },
+ }, {
+ parameters: "paths=source_relative,import_prefix=prefix",
+ wantFiles: map[string]bool{
+ // import_prefix doesn't affect filenames.
+ "alpha/a.pb.go": true,
+ "beta/b.pb.go": true,
+ },
}} {
name := test.parameters
if name == "" {