internal/importers: replace go/build usages with go/packages

This CL is a pure refactoring to replace go/build usage with
golang.org/x/tools/go/packages. This is a preparation for Go
modules.

Updates golang/go#27234

Change-Id: I3e6a30b962da1a64bc43a89a7f02c03d559f86d3
Reviewed-on: https://go-review.googlesource.com/c/mobile/+/189597
Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
diff --git a/cmd/gobind/gobind_test.go b/cmd/gobind/gobind_test.go
index 6ff40f7..7cdae3d 100644
--- a/cmd/gobind/gobind_test.go
+++ b/cmd/gobind/gobind_test.go
@@ -30,7 +30,7 @@
 	{"Go-Testpkg", "go", "golang.org/x/mobile/bind/testdata/testpkg", "", false},
 	{"Java-Javapkg", "java", "golang.org/x/mobile/bind/testdata/testpkg/javapkg", "android", true},
 	{"Go-Javapkg", "go", "golang.org/x/mobile/bind/testdata/testpkg/javapkg", "android", true},
-	{"Go-Javapkg", "go,java,objc", "golang.org/x/mobile/bind/testdata/cgopkg", "android", false},
+	{"Go-Cgopkg", "go,java,objc", "golang.org/x/mobile/bind/testdata/cgopkg", "android", false},
 }
 
 var gobindBin string
@@ -109,10 +109,16 @@
 		t.Fatal(err)
 	}
 
+	gopath, err := exec.Command("go", "env", "GOPATH").Output()
+	if err != nil {
+		t.Fatal(err)
+	}
+
 	const comment = "This is a comment."
 	for _, lang := range []string{"java", "objc"} {
 		cmd := exec.Command(gobindBin, "-lang", lang, "doctest")
-		cmd.Env = append(os.Environ(), "GOROOT="+tmpdir)
+		// TODO(hajimehoshi): Enable this test with Go modules.
+		cmd.Env = append(os.Environ(), "GOPATH="+tmpdir+string(filepath.ListSeparator)+string(gopath), "GO111MODULE=off")
 		out, err := cmd.CombinedOutput()
 		if err != nil {
 			t.Errorf("gobind -lang %s failed: %v: %s", lang, err, out)
diff --git a/cmd/gobind/main.go b/cmd/gobind/main.go
index 303938d..3c889d1 100644
--- a/cmd/gobind/main.go
+++ b/cmd/gobind/main.go
@@ -11,7 +11,6 @@
 	"go/ast"
 	"go/build"
 	"go/importer"
-	"go/parser"
 	"go/types"
 	"io/ioutil"
 	"log"
@@ -23,6 +22,7 @@
 	"golang.org/x/mobile/internal/importers"
 	"golang.org/x/mobile/internal/importers/java"
 	"golang.org/x/mobile/internal/importers/objc"
+	"golang.org/x/tools/go/packages"
 )
 
 var (
@@ -53,17 +53,21 @@
 	} else {
 		langs = []string{"go", "java", "objc"}
 	}
-	ctx := build.Default
-	if *tags != "" {
-		ctx.BuildTags = append(ctx.BuildTags, strings.Split(*tags, ",")...)
+
+	cfg := &packages.Config{
+		Mode: packages.NeedName | packages.NeedFiles | packages.NeedCompiledGoFiles |
+			packages.NeedImports | packages.NeedDeps |
+			packages.NeedTypes | packages.NeedSyntax | packages.NeedTypesInfo,
+		BuildFlags: []string{"-tags", *tags},
+
+		// packages.Load invokes `go list` command with `GOOS=android`, but in most cases
+		// go-list cannot find the header files for Android. Suppress this error by
+		// disabling Cgo.
+		Env: append(os.Environ(), "CGO_ENABLED=0"),
 	}
-	var allPkg []*build.Package
-	for _, path := range flag.Args() {
-		pkg, err := ctx.Import(path, ".", build.ImportComment)
-		if err != nil {
-			log.Fatalf("package %q: %v", path, err)
-		}
-		allPkg = append(allPkg, pkg)
+	allPkg, err := packages.Load(cfg, flag.Args()...)
+	if err != nil {
+		log.Fatal(err)
 	}
 	jrefs, err := importers.AnalyzePackages(allPkg, "Java/")
 	if err != nil {
@@ -92,6 +96,12 @@
 			log.Fatal(err)
 		}
 	}
+
+	ctx := build.Default
+	if *tags != "" {
+		ctx.BuildTags = append(ctx.BuildTags, strings.Split(*tags, ",")...)
+	}
+
 	// Determine GOPATH from go env GOPATH in case the default $HOME/go GOPATH
 	// is in effect.
 	if out, err := exec.Command("go", "env", "GOPATH").Output(); err != nil {
@@ -142,16 +152,12 @@
 	imp := importer.For("source", nil)
 	for i, pkg := range allPkg {
 		var err error
-		typePkgs[i], err = imp.Import(pkg.ImportPath)
+		typePkgs[i], err = imp.Import(pkg.PkgPath)
 		if err != nil {
 			errorf("%v\n", err)
 			return
 		}
-		astPkgs[i], err = parse(pkg)
-		if err != nil {
-			errorf("%v\n", err)
-			return
-		}
+		astPkgs[i] = pkg.Syntax
 	}
 	for _, l := range langs {
 		for i, pkg := range typePkgs {
@@ -162,19 +168,6 @@
 	}
 }
 
-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{}) {
diff --git a/go.mod b/go.mod
index 534449e..a1a0c61 100644
--- a/go.mod
+++ b/go.mod
@@ -5,4 +5,5 @@
 require (
 	golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56
 	golang.org/x/image v0.0.0-20190802002840-cff245a6509b
+	golang.org/x/tools v0.0.0-20190808195139-e713427fea3f
 )
diff --git a/go.sum b/go.sum
index 547f4b7..597a296 100644
--- a/go.sum
+++ b/go.sum
@@ -3,7 +3,6 @@
 golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56 h1:estk1glOnSVeJ9tdEZZc5mAMDZk5lNJNyJ6DvrBkTEU=
 golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4=
-golang.org/x/image v0.0.0-20190227222117-0694c2d4d067 h1:KYGJGHOQy8oSi1fDlSpcZF0+juKwk/hEMv5SiwHogR0=
 golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
 golang.org/x/image v0.0.0-20190802002840-cff245a6509b h1:+qEpEAPhDZ1o0x3tHzZTQDArnOixOzGD9HUJfcg0mb4=
 golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
@@ -11,8 +10,13 @@
 golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
 golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190808195139-e713427fea3f h1:lSQQYboXWc71s9tnZRRBiMcc9Uc1BPWj3Bzvdk8UQ0Y=
+golang.org/x/tools v0.0.0-20190808195139-e713427fea3f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
diff --git a/internal/importers/ast.go b/internal/importers/ast.go
index 7f1759b..fb80014 100644
--- a/internal/importers/ast.go
+++ b/internal/importers/ast.go
@@ -28,14 +28,13 @@
 import (
 	"errors"
 	"go/ast"
-	"go/build"
-	"go/parser"
 	"go/token"
 	"path"
-	"path/filepath"
 	"sort"
 	"strconv"
 	"strings"
+
+	"golang.org/x/tools/go/packages"
 )
 
 // References is the result of analyzing a Go file or set of Go packages.
@@ -101,24 +100,19 @@
 
 // AnalyzePackages scans the provided packages for references to packages with the given
 // package prefix. The list of unique (package, identifier) pairs is returned
-func AnalyzePackages(pkgs []*build.Package, pkgPrefix string) (*References, error) {
+func AnalyzePackages(pkgs []*packages.Package, pkgPrefix string) (*References, error) {
 	visitor := newRefsSaver(pkgPrefix)
 	imp := visitor.importer()
 	fset := token.NewFileSet()
 	for _, pkg := range pkgs {
-		fileNames := append(append([]string{}, pkg.GoFiles...), pkg.CgoFiles...)
 		files := make(map[string]*ast.File)
-		for _, name := range fileNames {
-			f, err := parser.ParseFile(fset, filepath.Join(pkg.Dir, name), nil, 0)
-			if err != nil {
-				return nil, err
-			}
-			files[name] = f
+		for i, name := range pkg.CompiledGoFiles {
+			files[name] = pkg.Syntax[i]
 		}
 		// Ignore errors (from unknown packages)
 		astpkg, _ := ast.NewPackage(fset, files, imp, nil)
 		ast.Walk(visitor, astpkg)
-		visitor.findEmbeddingStructs(pkg.ImportPath, astpkg)
+		visitor.findEmbeddingStructs(pkg.PkgPath, astpkg)
 	}
 	return visitor.References, nil
 }