cmd/gobind: copy documentation to generated source

CL 99316 moved generation of bindings from the the gomobile command
to the gobind command. In the process, the ability to copy over
documentation from the Go source to the derived Java and ObjC was
lost. The relevant test didn't fail because it tests the generator
itself, not gobind.

Re-add support and add a gobind test for it.

Fixes golang/go#25473

Change-Id: I6eee3e79173f37d3e3e65eabc0bad59e4252da64
Reviewed-on: https://go-review.googlesource.com/114056
Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
diff --git a/cmd/gobind/gen.go b/cmd/gobind/gen.go
index f1fc7b4..a47432a 100644
--- a/cmd/gobind/gen.go
+++ b/cmd/gobind/gen.go
@@ -7,6 +7,7 @@
 import (
 	"bytes"
 	"fmt"
+	"go/ast"
 	"go/build"
 	"go/token"
 	"go/types"
@@ -24,7 +25,7 @@
 	"golang.org/x/mobile/internal/importers/objc"
 )
 
-func genPkg(lang string, p *types.Package, allPkg []*types.Package, classes []*java.Class, otypes []*objc.Named) {
+func genPkg(lang string, p *types.Package, astFiles []*ast.File, allPkg []*types.Package, classes []*java.Class, otypes []*objc.Named) {
 	fname := defaultFileName(lang, p)
 	conf := &bind.GeneratorConfig{
 		Fset:   fset,
@@ -37,17 +38,19 @@
 	} else {
 		pname = "universe"
 	}
+	var buf bytes.Buffer
+	generator := &bind.Generator{
+		Printer: &bind.Printer{Buf: &buf, IndentEach: []byte("\t")},
+		Fset:    conf.Fset,
+		AllPkg:  conf.AllPkg,
+		Pkg:     conf.Pkg,
+		Files:   astFiles,
+	}
 	switch lang {
 	case "java":
-		var buf bytes.Buffer
 		g := &bind.JavaGen{
-			JavaPkg: *javaPkg,
-			Generator: &bind.Generator{
-				Printer: &bind.Printer{Buf: &buf, IndentEach: []byte("    ")},
-				Fset:    conf.Fset,
-				AllPkg:  conf.AllPkg,
-				Pkg:     conf.Pkg,
-			},
+			JavaPkg:   *javaPkg,
+			Generator: generator,
 		}
 		g.Init(classes)
 
@@ -112,7 +115,6 @@
 		conf.Writer = w
 		processErr(bind.GenGo(conf))
 		closer()
-		var buf bytes.Buffer
 		w, closer = writer(filepath.Join("src", "gobind", pname+".h"))
 		genPkgH(w, pname)
 		io.Copy(w, &buf)
@@ -128,15 +130,9 @@
 		}
 		copyFile(filepath.Join("src", "gobind", "seq.go"), filepath.Join(bindPkg.Dir, "seq.go.support"))
 	case "objc":
-		var buf bytes.Buffer
 		g := &bind.ObjcGen{
-			Generator: &bind.Generator{
-				Printer: &bind.Printer{Buf: &buf, IndentEach: []byte("\t")},
-				Fset:    conf.Fset,
-				AllPkg:  conf.AllPkg,
-				Pkg:     conf.Pkg,
-			},
-			Prefix: *prefix,
+			Generator: generator,
+			Prefix:    *prefix,
 		}
 		g.Init(otypes)
 		w, closer := writer(filepath.Join("src", "gobind", pname+"_darwin.h"))
diff --git a/cmd/gobind/gobind_test.go b/cmd/gobind/gobind_test.go
index ed3f598..f2ff706 100644
--- a/cmd/gobind/gobind_test.go
+++ b/cmd/gobind/gobind_test.go
@@ -5,9 +5,12 @@
 package main
 
 import (
+	"bytes"
 	"fmt"
+	"io/ioutil"
 	"os"
 	"os/exec"
+	"path/filepath"
 	"testing"
 )
 
@@ -57,6 +60,45 @@
 	}
 }
 
+func TestDocs(t *testing.T) {
+	if err := installGobind(); err != nil {
+		t.Fatal(err)
+	}
+	// Create a fake package for doc.go
+	tmpdir, err := ioutil.TempDir("", "gobind-test-")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer os.RemoveAll(tmpdir)
+	docPkg := filepath.Join(tmpdir, "src", "doctest")
+	if err := os.MkdirAll(docPkg, 0700); err != nil {
+		t.Fatal(err)
+	}
+	const docsrc = `
+package doctest
+
+// This is a comment.
+type Struct struct{
+}`
+	if err := ioutil.WriteFile(filepath.Join(docPkg, "doc.go"), []byte(docsrc), 0700); err != nil {
+		t.Fatal(err)
+	}
+
+	const comment = "This is a comment."
+	for _, lang := range []string{"java", "objc"} {
+		cmd := exec.Command("gobind", "-lang", lang, "doctest")
+		cmd.Env = append(os.Environ(), "GOROOT="+tmpdir)
+		out, err := cmd.CombinedOutput()
+		if err != nil {
+			t.Errorf("gobind -lang %s failed: %v: %s", lang, err, out)
+			continue
+		}
+		if bytes.Index(out, []byte(comment)) == -1 {
+			t.Errorf("gobind output for language %s did not contain the comment %q", lang, comment)
+		}
+	}
+}
+
 func BenchmarkGobind(b *testing.B) {
 	if err := installGobind(); err != nil {
 		b.Fatal(err)
diff --git a/cmd/gobind/main.go b/cmd/gobind/main.go
index 6e6d239..303938d 100644
--- a/cmd/gobind/main.go
+++ b/cmd/gobind/main.go
@@ -8,8 +8,10 @@
 	"bytes"
 	"flag"
 	"fmt"
+	"go/ast"
 	"go/build"
 	"go/importer"
+	"go/parser"
 	"go/types"
 	"io/ioutil"
 	"log"
@@ -130,6 +132,7 @@
 	}
 
 	typePkgs := make([]*types.Package, len(allPkg))
+	astPkgs := make([][]*ast.File, len(allPkg))
 	// The "source" go/importer package implicitly uses build.Default.
 	oldCtx := build.Default
 	build.Default = ctx
@@ -144,16 +147,34 @@
 			errorf("%v\n", err)
 			return
 		}
+		astPkgs[i], err = parse(pkg)
+		if err != nil {
+			errorf("%v\n", err)
+			return
+		}
 	}
 	for _, l := range langs {
-		for _, pkg := range typePkgs {
-			genPkg(l, pkg, typePkgs, classes, otypes)
+		for i, pkg := range typePkgs {
+			genPkg(l, pkg, astPkgs[i], typePkgs, classes, otypes)
 		}
 		// Generate the error package and support files
-		genPkg(l, nil, typePkgs, classes, otypes)
+		genPkg(l, nil, nil, typePkgs, classes, otypes)
 	}
 }
 
+func parse(pkg *build.Package) ([]*ast.File, error) {
+	fileNames := append(append([]string{}, pkg.GoFiles...), pkg.CgoFiles...)
+	var files []*ast.File
+	for _, name := range fileNames {
+		f, err := parser.ParseFile(fset, filepath.Join(pkg.Dir, name), nil, parser.ParseComments)
+		if err != nil {
+			return nil, err
+		}
+		files = append(files, f)
+	}
+	return files, nil
+}
+
 var exitStatus = 0
 
 func errorf(format string, args ...interface{}) {