cmd/gomobile: replace go/build with go/packages in bind

This is a preparation to use Go modules at gomobile-bind.

Updates golang/go#27234

Change-Id: I33684888b4181cc1ebd4d3c8872a6b2e62950855
Reviewed-on: https://go-review.googlesource.com/c/mobile/+/206777
Run-TryBot: Hajime Hoshi <hajimehoshi@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Michael Matloob <matloob@golang.org>
Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
diff --git a/cmd/gomobile/bind.go b/cmd/gomobile/bind.go
index ed1874a..54eb6f1 100644
--- a/cmd/gomobile/bind.go
+++ b/cmd/gomobile/bind.go
@@ -7,16 +7,18 @@
 import (
 	"errors"
 	"fmt"
-	"go/build"
 	"io"
 	"io/ioutil"
 	"os"
 	"os/exec"
 	"path"
 	"path/filepath"
+	"strings"
+
+	"golang.org/x/tools/go/packages"
 )
 
-// ctx, pkg, tmpdir in build.go
+// ctx in build.go
 
 var cmdBind = &command{
 	run:   runBind,
@@ -79,27 +81,25 @@
 		return fmt.Errorf(`invalid -target=%q: %v`, buildTarget, err)
 	}
 
+	// TODO(hajimehoshi): ctx is now used only for recording build tags in bind. Remove this.
 	oldCtx := ctx
 	defer func() {
 		ctx = oldCtx
 	}()
-	ctx.GOARCH = "arm"
-	ctx.GOOS = targetOS
 
-	if bindJavaPkg != "" && ctx.GOOS != "android" {
+	if bindJavaPkg != "" && targetOS != "android" {
 		return fmt.Errorf("-javapkg is supported only for android target")
 	}
-	if bindPrefix != "" && ctx.GOOS != "darwin" {
+	if bindPrefix != "" && targetOS != "darwin" {
 		return fmt.Errorf("-prefix is supported only for ios target")
 	}
 
-	if ctx.GOOS == "android" {
+	if targetOS == "android" {
 		if _, err := ndkRoot(); err != nil {
 			return err
 		}
 	}
-
-	if ctx.GOOS == "darwin" {
+	if targetOS == "darwin" {
 		ctx.BuildTags = append(ctx.BuildTags, "ios")
 	}
 
@@ -113,13 +113,12 @@
 		gobind = "gobind"
 	}
 
-	var pkgs []*build.Package
+	var pkgs []*packages.Package
 	switch len(args) {
 	case 0:
-		pkgs = make([]*build.Package, 1)
-		pkgs[0], err = ctx.ImportDir(cwd, build.ImportComment)
+		pkgs, err = packages.Load(packagesConfig(targetOS), cwd)
 	default:
-		pkgs, err = importPackages(args)
+		pkgs, err = importPackages(args, targetOS)
 	}
 	if err != nil {
 		return err
@@ -128,7 +127,7 @@
 	// check if any of the package is main
 	for _, pkg := range pkgs {
 		if pkg.Name == "main" {
-			return fmt.Errorf("binding 'main' package (%s) is not supported", pkg.ImportComment)
+			return fmt.Errorf("binding 'main' package (%s) is not supported", pkg.PkgPath)
 		}
 	}
 
@@ -145,16 +144,13 @@
 	}
 }
 
-func importPackages(args []string) ([]*build.Package, error) {
-	pkgs := make([]*build.Package, len(args))
-	for i, a := range args {
-		a = path.Clean(a)
-		var err error
-		if pkgs[i], err = ctx.Import(a, cwd, build.ImportComment); err != nil {
-			return nil, fmt.Errorf("package %q: %v", a, err)
-		}
+func importPackages(args []string, targetOS string) ([]*packages.Package, error) {
+	config := packagesConfig(targetOS)
+	var cleaned []string
+	for _, a := range args {
+		cleaned = append(cleaned, path.Clean(a))
 	}
-	return pkgs, nil
+	return packages.Load(config, cleaned...)
 }
 
 var (
@@ -232,3 +228,12 @@
 
 	return generate(f)
 }
+
+func packagesConfig(targetOS string) *packages.Config {
+	config := &packages.Config{}
+	config.Env = append(os.Environ(), "GOARCH=arm", "GOOS="+targetOS)
+	if len(ctx.BuildTags) > 0 {
+		config.BuildFlags = []string{"-tags=" + strings.Join(ctx.BuildTags, ",")}
+	}
+	return config
+}
diff --git a/cmd/gomobile/bind_androidapp.go b/cmd/gomobile/bind_androidapp.go
index 719e376..8bba0da 100644
--- a/cmd/gomobile/bind_androidapp.go
+++ b/cmd/gomobile/bind_androidapp.go
@@ -7,7 +7,6 @@
 import (
 	"archive/zip"
 	"fmt"
-	"go/build"
 	"io"
 	"io/ioutil"
 	"os"
@@ -15,9 +14,11 @@
 	"path/filepath"
 	"strconv"
 	"strings"
+
+	"golang.org/x/tools/go/packages"
 )
 
-func goAndroidBind(gobind string, pkgs []*build.Package, androidArchs []string) error {
+func goAndroidBind(gobind string, pkgs []*packages.Package, androidArchs []string) error {
 	if sdkDir := os.Getenv("ANDROID_HOME"); sdkDir == "" {
 		return fmt.Errorf("this command requires ANDROID_HOME environment variable (path to the Android SDK)")
 	}
@@ -43,7 +44,7 @@
 		cmd.Args = append(cmd.Args, "-bootclasspath="+bindBootClasspath)
 	}
 	for _, p := range pkgs {
-		cmd.Args = append(cmd.Args, p.ImportPath)
+		cmd.Args = append(cmd.Args, p.PkgPath)
 	}
 	if err := runCmd(cmd); err != nil {
 		return err
@@ -114,7 +115,7 @@
 //	aidl (optional, not relevant)
 //
 // javac and jar commands are needed to build classes.jar.
-func buildAAR(srcDir, androidDir string, pkgs []*build.Package, androidArchs []string) (err error) {
+func buildAAR(srcDir, androidDir string, pkgs []*packages.Package, androidArchs []string) (err error) {
 	var out io.Writer = ioutil.Discard
 	if buildO == "" {
 		buildO = pkgs[0].Name + ".aar"
@@ -173,7 +174,9 @@
 
 	files := map[string]string{}
 	for _, pkg := range pkgs {
-		assetsDir := filepath.Join(pkg.Dir, "assets")
+		// TODO(hajimehoshi): This works only with Go tools that assume all source files are in one directory.
+		// Fix this to work with other Go tools.
+		assetsDir := filepath.Join(filepath.Dir(pkg.GoFiles[0]), "assets")
 		assetsDirExists := false
 		if fi, err := os.Stat(assetsDir); err == nil {
 			assetsDirExists = fi.IsDir()
@@ -198,9 +201,9 @@
 					name := "assets/" + path[len(assetsDir)+1:]
 					if orig, exists := files[name]; exists {
 						return fmt.Errorf("package %s asset name conflict: %s already added from package %s",
-							pkg.ImportPath, name, orig)
+							pkg.PkgPath, name, orig)
 					}
-					files[name] = pkg.ImportPath
+					files[name] = pkg.PkgPath
 					w, err := aarwcreate(name)
 					if err != nil {
 						return nil
diff --git a/cmd/gomobile/bind_iosapp.go b/cmd/gomobile/bind_iosapp.go
index 2c129b0..69a1636 100644
--- a/cmd/gomobile/bind_iosapp.go
+++ b/cmd/gomobile/bind_iosapp.go
@@ -6,15 +6,16 @@
 
 import (
 	"fmt"
-	"go/build"
 	"io"
 	"os/exec"
 	"path/filepath"
 	"strings"
 	"text/template"
+
+	"golang.org/x/tools/go/packages"
 )
 
-func goIOSBind(gobind string, pkgs []*build.Package, archs []string) error {
+func goIOSBind(gobind string, pkgs []*packages.Package, archs []string) error {
 	// Run gobind to generate the bindings
 	cmd := exec.Command(
 		gobind,
@@ -30,7 +31,7 @@
 		cmd.Args = append(cmd.Args, "-prefix="+bindPrefix)
 	}
 	for _, p := range pkgs {
-		cmd.Args = append(cmd.Args, p.ImportPath)
+		cmd.Args = append(cmd.Args, p.PkgPath)
 	}
 	if err := runCmd(cmd); err != nil {
 		return err
@@ -188,7 +189,7 @@
 var iosBindHeaderTmpl = template.Must(template.New("ios.h").Parse(`
 // Objective-C API for talking to the following Go packages
 //
-{{range .pkgs}}//	{{.ImportPath}}
+{{range .pkgs}}//	{{.PkgPath}}
 {{end}}//
 // File is generated by gomobile bind. Do not edit.
 #ifndef __{{.title}}_FRAMEWORK_H__
diff --git a/cmd/gomobile/bind_test.go b/cmd/gomobile/bind_test.go
index 72ab352..bb3e20c 100644
--- a/cmd/gomobile/bind_test.go
+++ b/cmd/gomobile/bind_test.go
@@ -20,13 +20,13 @@
 		t.Skip("not available on Android")
 	}
 	slashPath := "golang.org/x/mobile/example/bind/hello/"
-	pkgs, err := importPackages([]string{slashPath})
+	pkgs, err := importPackages([]string{slashPath}, runtime.GOOS)
 	if err != nil {
 		t.Fatal(err)
 	}
 	p := pkgs[0]
-	if c := path.Clean(slashPath); p.ImportPath != c {
-		t.Errorf("expected %s; got %s", c, p.ImportPath)
+	if c := path.Clean(slashPath); p.PkgPath != c {
+		t.Errorf("expected %s; got %s", c, p.PkgPath)
 	}
 }
 
@@ -145,8 +145,7 @@
 			os.Setenv("HOMEDRIVE", "C:")
 		}
 		cmdBind.flag.Parse([]string{"golang.org/x/mobile/asset"})
-		err := runBind(cmdBind)
-		if err != nil {
+		if err := runBind(cmdBind); err != nil {
 			t.Log(buf.String())
 			t.Fatal(err)
 		}