compiler/protogen: add module= generator option

Add a generator option that strips a prefix from the generated
filenames.

Consider this case: We have google/protobuf/empty.proto, with a
go_package option of "google.golang.org/protobuf/types/known/emptypb".
We want to generate the code for this file, placing it into the
appropriate directory of our repository.

In the default mode used by the code generator (paths=import),
the generator outputs the file:

	google.golang.org/protobuf/types/known/emptypb/empty.pb.go

This is close to what we want, but has an unnecessary
"google.golang.org/protobuf/" prefix. In the GOPATH world, we could pass
--go_out=$GOPATH to protoc and get a generated file in the desired
location, but this path is not useful in the modules world.

The 'module' option allows us to strip off the module prefix, generating
the desired filename (types/known/emptypb/empty.pb.go):

	protoc --go_out=. --go_opt=module=google.golang.org/protobuf google/protobuf/empty.proto

The module name must be an exact, character-for-character match. This
matches protoc's file handling in general.

Default to and require the paths=import option when module= is
specified, since it only makes sense when combined with it.

Updates golang/protobuf#992.

Change-Id: Idbfe4826b6c0ece30d64dbc577131a4f16391936
Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/219298
Reviewed-by: Joe Tsai <thebrokentoaster@gmail.com>
diff --git a/compiler/protogen/protogen.go b/compiler/protogen/protogen.go
index 5cd2a80..118be73 100644
--- a/compiler/protogen/protogen.go
+++ b/compiler/protogen/protogen.go
@@ -109,6 +109,7 @@
 	messagesByName map[protoreflect.FullName]*Message
 	annotateCode   bool
 	pathType       pathType
+	module         string
 	genFiles       []*GeneratedFile
 	opts           Options
 	err            error
@@ -172,6 +173,8 @@
 			// Ignore.
 		case "import_path":
 			packageImportPath = GoImportPath(value)
+		case "module":
+			gen.module = value
 		case "paths":
 			switch value {
 			case "import":
@@ -210,6 +213,18 @@
 			}
 		}
 	}
+	if gen.module != "" {
+		// When the module= option is provided, we strip the module name
+		// prefix from generated files. This only makes sense if generated
+		// filenames are based on the import path, so default to paths=import
+		// and complain if source_relative was selected manually.
+		switch gen.pathType {
+		case pathTypeLegacy:
+			gen.pathType = pathTypeImport
+		case pathTypeSourceRelative:
+			return nil, fmt.Errorf("cannot use module= with paths=source_relative")
+		}
+	}
 
 	// Figure out the import path and package name for each file.
 	//
@@ -396,8 +411,18 @@
 				Error: proto.String(err.Error()),
 			}
 		}
+		filename := g.filename
+		if gen.module != "" {
+			trim := gen.module + "/"
+			if !strings.HasPrefix(filename, trim) {
+				return &pluginpb.CodeGeneratorResponse{
+					Error: proto.String(fmt.Sprintf("%v: generated file does not match prefix %q", filename, gen.module)),
+				}
+			}
+			filename = strings.TrimPrefix(filename, trim)
+		}
 		resp.File = append(resp.File, &pluginpb.CodeGeneratorResponse_File{
-			Name:    proto.String(g.filename),
+			Name:    proto.String(filename),
 			Content: proto.String(string(content)),
 		})
 		if gen.annotateCode && strings.HasSuffix(g.filename, ".go") {
@@ -408,7 +433,7 @@
 				}
 			}
 			resp.File = append(resp.File, &pluginpb.CodeGeneratorResponse_File{
-				Name:    proto.String(g.filename + ".meta"),
+				Name:    proto.String(filename + ".meta"),
 				Content: proto.String(meta),
 			})
 		}