cmd/protoc-gen-go: generate blank imports for unused proto dependencies

Generate Go imports for all packages imported by the .proto source file,
even if they are not referenced.

Change-Id: I116bdf460ab441d205b42606b2b05b315ed68954
Reviewed-on: https://go-review.googlesource.com/136358
Reviewed-by: Joe Tsai <thebrokentoaster@gmail.com>
diff --git a/cmd/protoc-gen-go/main.go b/cmd/protoc-gen-go/main.go
index b477fe7..8c73c0c 100644
--- a/cmd/protoc-gen-go/main.go
+++ b/cmd/protoc-gen-go/main.go
@@ -148,15 +148,19 @@
 }
 
 func genImport(gen *protogen.Plugin, g *protogen.GeneratedFile, f *File, imp protoreflect.FileImport) {
-	if !imp.IsPublic {
-		return
-	}
 	impFile, ok := gen.FileByName(imp.Path())
 	if !ok {
 		return
 	}
 	if impFile.GoImportPath == f.GoImportPath {
-		// Don't generate aliases for types in the same Go package.
+		// Don't generate imports or aliases for types in the same Go package.
+		return
+	}
+	// Generate imports for all dependencies, even if they are not
+	// referenced, because other code and tools depend on having the
+	// full transitive closure of protocol buffer types in the binary.
+	g.Import(impFile.GoImportPath)
+	if !imp.IsPublic {
 		return
 	}
 	var enums []*protogen.Enum
diff --git a/cmd/protoc-gen-go/testdata/imports/test_import_all.pb.go b/cmd/protoc-gen-go/testdata/imports/test_import_all.pb.go
index 1746f9d..61699b2 100644
--- a/cmd/protoc-gen-go/testdata/imports/test_import_all.pb.go
+++ b/cmd/protoc-gen-go/testdata/imports/test_import_all.pb.go
@@ -8,7 +8,7 @@
 	proto "github.com/golang/protobuf/proto"
 	fmt1 "github.com/golang/protobuf/protoc-gen-go/testdata/imports/fmt"
 	test_a_1 "github.com/golang/protobuf/protoc-gen-go/testdata/imports/test_a_1"
-	test_a_2 "github.com/golang/protobuf/protoc-gen-go/testdata/imports/test_a_2"
+	_ "github.com/golang/protobuf/protoc-gen-go/testdata/imports/test_a_2"
 	test_b_1 "github.com/golang/protobuf/protoc-gen-go/testdata/imports/test_b_1"
 	math "math"
 )
@@ -27,8 +27,6 @@
 type All struct {
 	Am1                  *test_a_1.M1 `protobuf:"bytes,1,opt,name=am1,proto3" json:"am1,omitempty"`
 	Am2                  *test_a_1.M2 `protobuf:"bytes,2,opt,name=am2,proto3" json:"am2,omitempty"`
-	Am3                  *test_a_2.M3 `protobuf:"bytes,3,opt,name=am3,proto3" json:"am3,omitempty"`
-	Am4                  *test_a_2.M4 `protobuf:"bytes,4,opt,name=am4,proto3" json:"am4,omitempty"`
 	Bm1                  *test_b_1.M1 `protobuf:"bytes,5,opt,name=bm1,proto3" json:"bm1,omitempty"`
 	Bm2                  *test_b_1.M2 `protobuf:"bytes,6,opt,name=bm2,proto3" json:"bm2,omitempty"`
 	Fmt                  *fmt1.M      `protobuf:"bytes,7,opt,name=fmt,proto3" json:"fmt,omitempty"`
@@ -76,20 +74,6 @@
 	return nil
 }
 
-func (m *All) GetAm3() *test_a_2.M3 {
-	if m != nil {
-		return m.Am3
-	}
-	return nil
-}
-
-func (m *All) GetAm4() *test_a_2.M4 {
-	if m != nil {
-		return m.Am4
-	}
-	return nil
-}
-
 func (m *All) GetBm1() *test_b_1.M1 {
 	if m != nil {
 		return m.Bm1
@@ -118,22 +102,21 @@
 func init() { proto.RegisterFile("imports/test_import_all.proto", fileDescriptor_324466f0afc16f77) }
 
 var fileDescriptor_324466f0afc16f77 = []byte{
-	// 258 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0xd0, 0xb1, 0x4e, 0xc3, 0x30,
-	0x10, 0x06, 0x60, 0x15, 0x97, 0x20, 0x99, 0x05, 0x85, 0xc5, 0x20, 0x90, 0x50, 0x27, 0x96, 0xda,
-	0xb2, 0x9d, 0x05, 0x31, 0xc1, 0xde, 0xa5, 0x23, 0x4b, 0x64, 0x97, 0xc6, 0x54, 0xf2, 0xd5, 0x51,
-	0x7a, 0x7d, 0x5e, 0x5e, 0x05, 0xd9, 0x07, 0x12, 0x84, 0x66, 0x4b, 0xfe, 0xef, 0xb7, 0xce, 0x3e,
-	0x7e, 0xbf, 0x83, 0x3e, 0x0d, 0x78, 0x50, 0xb8, 0x3d, 0x60, 0x4b, 0x3f, 0xad, 0x8b, 0x51, 0xf6,
-	0x43, 0xc2, 0x54, 0xcf, 0x73, 0x7c, 0x7b, 0xf3, 0xa7, 0xe4, 0x5a, 0xad, 0x40, 0x53, 0xe1, 0x14,
-	0x99, 0x09, 0x32, 0x0a, 0xec, 0x34, 0x35, 0x27, 0xc9, 0x4f, 0xcf, 0xf2, 0xbf, 0x67, 0x5d, 0xff,
-	0x50, 0x07, 0xa8, 0x80, 0xc2, 0xc5, 0xe7, 0x8c, 0xb3, 0x97, 0x18, 0xeb, 0x3b, 0xce, 0x1c, 0x68,
-	0x31, 0x7b, 0x98, 0x3d, 0x5e, 0x1a, 0x2e, 0xf3, 0x69, 0xe9, 0xe4, 0x4a, 0xaf, 0x73, 0x4c, 0x6a,
-	0xc4, 0xd9, 0x48, 0x4d, 0x56, 0x43, 0x6a, 0x05, 0x1b, 0xa9, 0xcd, 0x6a, 0x49, 0x1b, 0x31, 0x1f,
-	0x69, 0x93, 0xb5, 0xa9, 0x17, 0x9c, 0x79, 0xd0, 0xe2, 0xbc, 0xe8, 0x15, 0xa9, 0x97, 0xbd, 0x1b,
-	0x50, 0x97, 0xe9, 0x1e, 0x34, 0x75, 0x8c, 0xa8, 0xfe, 0x77, 0x4c, 0xb9, 0x83, 0x07, 0x53, 0x0b,
-	0xce, 0x3a, 0x40, 0x71, 0x51, 0x3a, 0x95, 0xec, 0x00, 0xe5, 0x6a, 0x9d, 0xa3, 0xd7, 0xe7, 0xb7,
-	0xa7, 0xb0, 0xc3, 0x8f, 0xa3, 0x97, 0x9b, 0x04, 0x2a, 0xa4, 0xe8, 0xf6, 0x41, 0x95, 0xc7, 0xfb,
-	0x63, 0x47, 0x1f, 0x9b, 0x65, 0xd8, 0xee, 0x97, 0x21, 0x95, 0xa5, 0xbd, 0x3b, 0x74, 0xea, 0x7b,
-	0x55, 0xbe, 0x2a, 0x6e, 0xbf, 0x02, 0x00, 0x00, 0xff, 0xff, 0x95, 0x39, 0xa3, 0x82, 0x03, 0x02,
+	// 242 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0xcf, 0xb1, 0x4e, 0xc3, 0x30,
+	0x10, 0x06, 0x60, 0x85, 0x40, 0x90, 0xcc, 0x82, 0xc2, 0x62, 0x10, 0x48, 0xa8, 0x13, 0x4b, 0x6d,
+	0xf9, 0xca, 0x82, 0x98, 0x60, 0xef, 0xd2, 0x91, 0x25, 0xf2, 0x95, 0xda, 0x54, 0xf2, 0xd5, 0x51,
+	0x7a, 0x7d, 0x22, 0x5e, 0x14, 0xd9, 0x06, 0x09, 0x68, 0xb2, 0x25, 0xff, 0xf7, 0xfb, 0x4e, 0x27,
+	0xee, 0xb6, 0xd4, 0xc7, 0x81, 0xf7, 0x9a, 0x37, 0x7b, 0xee, 0xca, 0x4f, 0x67, 0x43, 0x50, 0xfd,
+	0x10, 0x39, 0xb6, 0xa7, 0x29, 0xbe, 0xb9, 0xfe, 0x53, 0xb2, 0x9d, 0xd1, 0x64, 0x4a, 0x61, 0x8c,
+	0x60, 0x82, 0x40, 0xd3, 0x62, 0x9a, 0x1e, 0x47, 0x09, 0xa7, 0x77, 0xe1, 0xef, 0x5d, 0x57, 0x3f,
+	0xe4, 0x88, 0x35, 0x95, 0x70, 0xf6, 0x59, 0x89, 0xfa, 0x25, 0x84, 0xf6, 0x56, 0xd4, 0x96, 0x8c,
+	0xac, 0xee, 0xab, 0x87, 0x0b, 0x10, 0x2a, 0xbd, 0x56, 0x56, 0x2d, 0xcd, 0x2a, 0xc5, 0x45, 0x41,
+	0x9e, 0xfc, 0x53, 0x48, 0x0a, 0xed, 0x4c, 0xd4, 0x48, 0x46, 0x9e, 0x65, 0xbd, 0x2c, 0x8a, 0xaa,
+	0xb7, 0x03, 0x9b, 0x3c, 0x01, 0xc9, 0x94, 0x0e, 0xc8, 0xe6, 0xb8, 0x03, 0x79, 0x0e, 0x12, 0xb4,
+	0x52, 0xd4, 0x8e, 0x58, 0x9e, 0xe7, 0x4e, 0xa3, 0x1c, 0xb1, 0x5a, 0xae, 0x52, 0xf4, 0xfa, 0xfc,
+	0xf6, 0xe4, 0xb7, 0xfc, 0x71, 0x40, 0xb5, 0x8e, 0xa4, 0x7d, 0x0c, 0x76, 0xe7, 0x75, 0x3e, 0x00,
+	0x0f, 0xae, 0x7c, 0xac, 0xe7, 0x7e, 0xb3, 0x9b, 0xfb, 0x98, 0x0f, 0x7f, 0xb7, 0x6c, 0xf5, 0xf7,
+	0xb9, 0xd8, 0x64, 0x5f, 0x7c, 0x05, 0x00, 0x00, 0xff, 0xff, 0x88, 0x0e, 0xe2, 0x9f, 0xc7, 0x01,
 	0x00, 0x00,
 }
diff --git a/cmd/protoc-gen-go/testdata/imports/test_import_all.proto b/cmd/protoc-gen-go/testdata/imports/test_import_all.proto
index 7b62a69..b7258e4 100644
--- a/cmd/protoc-gen-go/testdata/imports/test_import_all.proto
+++ b/cmd/protoc-gen-go/testdata/imports/test_import_all.proto
@@ -14,8 +14,8 @@
 // fmt/m.proto has a package name which conflicts with "fmt".
 import "imports/test_a_1/m1.proto";
 import "imports/test_a_1/m2.proto";
-import "imports/test_a_2/m3.proto";
-import "imports/test_a_2/m4.proto";
+import "imports/test_a_2/m3.proto"; // unused in this file
+import "imports/test_a_2/m4.proto"; // unused in this file
 import "imports/test_b_1/m1.proto";
 import "imports/test_b_1/m2.proto";
 import "imports/fmt/m.proto";
@@ -23,8 +23,6 @@
 message All {
   test.a.M1 am1 = 1;
   test.a.M2 am2 = 2;
-  test.a.M3 am3 = 3;
-  test.a.M4 am4 = 4;
   test.b.part1.M1 bm1 = 5;
   test.b.part2.M2 bm2 = 6;
   fmt.M fmt = 7;
diff --git a/protogen/protogen.go b/protogen/protogen.go
index 753325e..9db98b1 100644
--- a/protogen/protogen.go
+++ b/protogen/protogen.go
@@ -706,6 +706,7 @@
 	buf              bytes.Buffer
 	packageNames     map[GoImportPath]GoPackageName
 	usedPackageNames map[GoPackageName]bool
+	manualImports    map[GoImportPath]bool
 }
 
 // NewGeneratedFile creates a new generated file with the given filename
@@ -716,6 +717,7 @@
 		goImportPath:     goImportPath,
 		packageNames:     make(map[GoImportPath]GoPackageName),
 		usedPackageNames: make(map[GoPackageName]bool),
+		manualImports:    make(map[GoImportPath]bool),
 	}
 	gen.genFiles = append(gen.genFiles, g)
 	return g
@@ -759,6 +761,15 @@
 	return string(packageName) + "." + ident.GoName
 }
 
+// Import ensures a package is imported by the generated file.
+//
+// Packages referenced by QualifiedGoIdent are automatically imported.
+// Explicitly importing a package with Import is generally only necessary
+// when the import will be blank (import _ "package").
+func (g *GeneratedFile) Import(importPath GoImportPath) {
+	g.manualImports[importPath] = true
+}
+
 // Write implements io.Writer.
 func (g *GeneratedFile) Write(p []byte) (n int, err error) {
 	return g.buf.Write(p)
@@ -795,6 +806,12 @@
 	for _, importPath := range importPaths {
 		astutil.AddNamedImport(fset, file, string(g.packageNames[GoImportPath(importPath)]), importPath)
 	}
+	for importPath := range g.manualImports {
+		if _, ok := g.packageNames[importPath]; ok {
+			continue
+		}
+		astutil.AddNamedImport(fset, file, "_", string(importPath))
+	}
 	ast.SortImports(fset, file)
 
 	var out bytes.Buffer