compiler/protogen: deprecate certain ways to derive Go package information

There are currently at least 5 ways to derive Go package information:
* From the 'M' command-line flag.
* From the 'import_path' command-line flag.
* From the 'go_package' proto file option.
* From the proto package name in proto source file.
* From the path of the proto source file.
Technically, there are more than 5 ways since information can be derived
from a convoluted combination of all the methods.

We should move towards a sensible and consistent world where each
Go package information for each proto source file is:
* only derived from the command-line OR
* only derived from the proto source file itself.
It should never be derived from a mixture of methods.

In the future, all .proto source files will be required to have a
"go_package" option specified, unless the "M" command-line argument is
specified. If the "M" flag is given it takes precedence over 'go_package'.

This CL prints warnings if the user is generating proto files without
a "M" flag or "go_package" option specified. It suggests to the user
a "go_package" option that preserves the current semantics.

Change-Id: I5cf8d40a245146bb145b3b610d42f1bcd140b449
Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/194158
Reviewed-by: Damien Neil <dneil@google.com>
diff --git a/compiler/protogen/protogen.go b/compiler/protogen/protogen.go
index 3be898d..5dbdaf9 100644
--- a/compiler/protogen/protogen.go
+++ b/compiler/protogen/protogen.go
@@ -21,6 +21,7 @@
 	"go/token"
 	"go/types"
 	"io/ioutil"
+	"log"
 	"os"
 	"path"
 	"path/filepath"
@@ -164,6 +165,7 @@
 
 	packageNames := make(map[string]GoPackageName) // filename -> package name
 	importPaths := make(map[string]GoImportPath)   // filename -> import path
+	mfiles := make(map[string]bool)                // filename set
 	var packageImportPath GoImportPath
 	for _, param := range strings.Split(req.GetParameter(), ",") {
 		var value string
@@ -196,6 +198,7 @@
 		default:
 			if param[0] == 'M' {
 				importPaths[param[1:]] = GoImportPath(value)
+				mfiles[param[1:]] = true
 				continue
 			}
 			if opts.ParamFunc != nil {
@@ -261,10 +264,16 @@
 	}
 	for _, fdesc := range gen.Request.ProtoFile {
 		filename := fdesc.GetName()
-		packageName, _ := goPackageOption(fdesc)
+		packageName, importPath := goPackageOption(fdesc)
 		defaultPackageName := packageNameForImportPath[importPaths[filename]]
 		switch {
 		case packageName != "":
+			// TODO: For the "M" command-line argument, this means that the
+			// package name can be derived from the go_package option.
+			// Go package information should either consistently come from the
+			// command-line or the .proto source file, but not both.
+			// See how to make this consistent.
+
 			// Source file: option go_package = "quux/bar";
 			packageNames[filename] = packageName
 		case defaultPackageName != "":
@@ -284,6 +293,33 @@
 			// Source filename.
 			packageNames[filename] = cleanPackageName(baseName(filename))
 		}
+
+		goPkgOpt := string(importPaths[filename])
+		if path.Base(string(goPkgOpt)) != string(packageNames[filename]) {
+			goPkgOpt += ";" + string(packageNames[filename])
+		}
+		switch {
+		case packageImportPath != "":
+			// Command line: import_path=quux/bar
+			log.Printf("WARNING: Deprecated use of the 'import_path' command-line argument. In %q, please specify:\n"+
+				"\toption go_package = %q;\n"+
+				"A future release of protoc-gen-go will no longer support the 'import_path' argument.\n"+
+				"\n", fdesc.GetName(), goPkgOpt)
+		case mfiles[filename]:
+			// Command line: M=foo.proto=quux/bar
+		case packageName != "" && importPath == "":
+			// Source file: option go_package = "quux";
+			log.Printf("WARNING: Deprecated use of 'go_package' option without a full import path in %q, please specify:\n"+
+				"\toption go_package = %q;\n"+
+				"A future release of protoc-gen-go will require the import path be specified.\n"+
+				"\n", fdesc.GetName(), goPkgOpt)
+		case packageName == "" && importPath == "":
+			// No Go package information provided.
+			log.Printf("WARNING: Missing 'go_package' option in %q, please specify:\n"+
+				"\toption go_package = %q;\n"+
+				"A future release of protoc-gen-go will require this be specified.\n"+
+				"\n", fdesc.GetName(), goPkgOpt)
+		}
 	}
 
 	// Consistency check: Every file with the same Go import path should have
@@ -498,17 +534,26 @@
 	if opt == "" {
 		return "", ""
 	}
+	rawPkg, impPath := goPackageOptionRaw(opt)
+	pkg = cleanPackageName(rawPkg)
+	if string(pkg) != rawPkg && impPath != "" {
+		log.Printf("WARNING: Malformed 'go_package' option in %q, please specify:\n"+
+			"\toption go_package = %q;\n"+
+			"A future release of protoc-gen-go will reject this.\n"+
+			"\n", d.GetName(), string(impPath)+";"+string(pkg))
+	}
+	return pkg, impPath
+}
+func goPackageOptionRaw(opt string) (rawPkg string, impPath GoImportPath) {
 	// A semicolon-delimited suffix delimits the import path and package name.
 	if i := strings.Index(opt, ";"); i >= 0 {
-		// TODO: The package name is explicitly provided by the .proto file.
-		// Rather than sanitizing it, we should pass it verbatim.
-		return cleanPackageName(opt[i+1:]), GoImportPath(opt[:i])
+		return opt[i+1:], GoImportPath(opt[:i])
 	}
 	// The presence of a slash implies there's an import path.
 	if i := strings.LastIndex(opt, "/"); i >= 0 {
-		return cleanPackageName(opt[i+1:]), GoImportPath(opt)
+		return opt[i+1:], GoImportPath(opt)
 	}
-	return cleanPackageName(opt), ""
+	return opt, ""
 }
 
 // An Enum describes an enum.