compiler/protogen: allow overriding API level from --go_opt

For golang/protobuf#1657

In order to change the default API level, one can now specify:

    protoc […] --go_opt=default_api_level=API_HYBRID

To override the default API level for a specific file, use
the apilevelM mapping flag (similar to the M flag for import paths):

    protoc […] --go_opt=apilevelMhello.proto=API_HYBRID

(Similar to the M option.)

Change-Id: I44590e9aa4c034a5bb9c93ae32f4b11188e684a0
Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/634818
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Damien Neil <dneil@google.com>
diff --git a/compiler/protogen/protogen.go b/compiler/protogen/protogen.go
index 0bff637..f7f295a 100644
--- a/compiler/protogen/protogen.go
+++ b/compiler/protogen/protogen.go
@@ -183,8 +183,9 @@
 		opts:           opts,
 	}
 
-	packageNames := make(map[string]GoPackageName) // filename -> package name
-	importPaths := make(map[string]GoImportPath)   // filename -> import path
+	packageNames := make(map[string]GoPackageName)                // filename -> package name
+	importPaths := make(map[string]GoImportPath)                  // filename -> import path
+	apiLevel := make(map[string]gofeaturespb.GoFeatures_APILevel) // filename -> api level
 	for _, param := range strings.Split(req.GetParameter(), ",") {
 		var value string
 		if i := strings.Index(param, "="); i >= 0 {
@@ -213,6 +214,18 @@
 			default:
 				return nil, fmt.Errorf(`bad value for parameter %q: want "true" or "false"`, param)
 			}
+		case "default_api_level":
+			switch value {
+			case "API_OPEN":
+				opts.DefaultAPILevel = gofeaturespb.GoFeatures_API_OPEN
+			case "API_HYBRID":
+				opts.DefaultAPILevel = gofeaturespb.GoFeatures_API_HYBRID
+			case "API_OPAQUE":
+				opts.DefaultAPILevel = gofeaturespb.GoFeatures_API_OPAQUE
+			default:
+				return nil, fmt.Errorf(`unknown API level %q for parameter %q: want "API_OPEN", "API_HYBRID" or "API_OPAQUE"`, value, param)
+			}
+			gen.opts = opts
 		default:
 			if param[0] == 'M' {
 				impPath, pkgName := splitImportPathAndPackageName(value)
@@ -224,6 +237,21 @@
 				}
 				continue
 			}
+			if strings.HasPrefix(param, "apilevelM") {
+				var level gofeaturespb.GoFeatures_APILevel
+				switch value {
+				case "API_OPEN":
+					level = gofeaturespb.GoFeatures_API_OPEN
+				case "API_HYBRID":
+					level = gofeaturespb.GoFeatures_API_HYBRID
+				case "API_OPAQUE":
+					level = gofeaturespb.GoFeatures_API_OPAQUE
+				default:
+					return nil, fmt.Errorf(`unknown API level %q for parameter %q: want "API_OPEN", "API_HYBRID" or "API_OPAQUE"`, value, param)
+				}
+				apiLevel[strings.TrimPrefix(param, "apilevelM")] = level
+				continue
+			}
 			if opts.ParamFunc != nil {
 				if err := opts.ParamFunc(param, value); err != nil {
 					return nil, err
@@ -328,7 +356,7 @@
 		if gen.FilesByPath[filename] != nil {
 			return nil, fmt.Errorf("duplicate file name: %q", filename)
 		}
-		f, err := newFile(gen, fdesc, packageNames[filename], importPaths[filename])
+		f, err := newFile(gen, fdesc, packageNames[filename], importPaths[filename], apiLevel[filename])
 		if err != nil {
 			return nil, err
 		}
@@ -469,7 +497,7 @@
 	APILevel gofeaturespb.GoFeatures_APILevel
 }
 
-func newFile(gen *Plugin, p *descriptorpb.FileDescriptorProto, packageName GoPackageName, importPath GoImportPath) (*File, error) {
+func newFile(gen *Plugin, p *descriptorpb.FileDescriptorProto, packageName GoPackageName, importPath GoImportPath, apiLevel gofeaturespb.GoFeatures_APILevel) (*File, error) {
 	desc, err := protodesc.NewFile(p, gen.fileReg)
 	if err != nil {
 		return nil, fmt.Errorf("invalid FileDescriptorProto %q: %v", p.GetName(), err)
@@ -477,6 +505,10 @@
 	if err := gen.fileReg.RegisterFile(desc); err != nil {
 		return nil, fmt.Errorf("cannot register descriptor %q: %v", p.GetName(), err)
 	}
+	defaultAPILevel := gen.defaultAPILevel()
+	if apiLevel != gofeaturespb.GoFeatures_API_LEVEL_UNSPECIFIED {
+		defaultAPILevel = apiLevel
+	}
 	f := &File{
 		Desc:          desc,
 		Proto:         p,
@@ -484,7 +516,7 @@
 		GoImportPath:  importPath,
 		location:      Location{SourceFile: desc.Path()},
 
-		APILevel: fileAPILevel(desc, gen.defaultAPILevel()),
+		APILevel: fileAPILevel(desc, defaultAPILevel),
 	}
 
 	// Determine the prefix for generated Go files.