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

This is a preparation to use Go modules at gomobile command.

Updates golang/go#27234

Change-Id: I8ee47cb53f5b748592a0c8c9f383abab27a7fdad
Reviewed-on: https://go-review.googlesource.com/c/mobile/+/208059
Run-TryBot: Hajime Hoshi <hajimehoshi@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
Reviewed-by: Daniel Martí <mvdan@mvdan.cc>
diff --git a/cmd/gomobile/bind.go b/cmd/gomobile/bind.go
index 54eb6f1..2910d8d 100644
--- a/cmd/gomobile/bind.go
+++ b/cmd/gomobile/bind.go
@@ -116,7 +116,7 @@
 	var pkgs []*packages.Package
 	switch len(args) {
 	case 0:
-		pkgs, err = packages.Load(packagesConfig(targetOS), cwd)
+		pkgs, err = packages.Load(packagesConfig(targetOS), ".")
 	default:
 		pkgs, err = importPackages(args, targetOS)
 	}
diff --git a/cmd/gomobile/build.go b/cmd/gomobile/build.go
index 449dc9f..59aa286 100644
--- a/cmd/gomobile/build.go
+++ b/cmd/gomobile/build.go
@@ -13,8 +13,11 @@
 	"io"
 	"os"
 	"os/exec"
+	"path"
 	"regexp"
 	"strings"
+
+	"golang.org/x/tools/go/packages"
 )
 
 var ctx = build.Default
@@ -72,7 +75,7 @@
 
 // runBuildImpl builds a package for mobiles based on the given commands.
 // runBuildImpl returns a built package information and an error if exists.
-func runBuildImpl(cmd *command) (*build.Package, error) {
+func runBuildImpl(cmd *command) (*packages.Package, error) {
 	cleanup, err := buildEnvInit()
 	if err != nil {
 		return nil, err
@@ -86,30 +89,37 @@
 		return nil, fmt.Errorf(`invalid -target=%q: %v`, buildTarget, err)
 	}
 
+	// TODO(hajimehoshi): ctx is now used only for recording build tags in build. Remove this.
 	oldCtx := ctx
 	defer func() {
 		ctx = oldCtx
 	}()
-	ctx.GOARCH = targetArchs[0]
-	ctx.GOOS = targetOS
 
-	if ctx.GOOS == "darwin" {
+	if targetOS == "darwin" {
 		ctx.BuildTags = append(ctx.BuildTags, "ios")
 	}
 
-	var pkg *build.Package
+	var buildPath string
 	switch len(args) {
 	case 0:
-		pkg, err = ctx.ImportDir(cwd, build.ImportComment)
+		buildPath = "."
 	case 1:
-		pkg, err = ctx.Import(args[0], cwd, build.ImportComment)
+		buildPath = path.Clean(args[0])
 	default:
 		cmd.usage()
 		os.Exit(1)
 	}
+	pkgs, err := packages.Load(packagesConfig(targetOS), buildPath)
 	if err != nil {
 		return nil, err
 	}
+	// len(pkgs) can be more than 1 e.g., when the specified path includes `...`.
+	if len(pkgs) != 1 {
+		cmd.usage()
+		os.Exit(1)
+	}
+
+	pkg := pkgs[0]
 
 	if pkg.Name != "main" && buildO != "" {
 		return nil, fmt.Errorf("cannot set -o when building non-main package")
@@ -121,7 +131,7 @@
 		if pkg.Name != "main" {
 			for _, arch := range targetArchs {
 				env := androidEnv[arch]
-				if err := goBuild(pkg.ImportPath, env); err != nil {
+				if err := goBuild(pkg.PkgPath, env); err != nil {
 					return nil, err
 				}
 			}
@@ -138,7 +148,7 @@
 		if pkg.Name != "main" {
 			for _, arch := range targetArchs {
 				env := darwinEnv[arch]
-				if err := goBuild(pkg.ImportPath, env); err != nil {
+				if err := goBuild(pkg.PkgPath, env); err != nil {
 					return nil, err
 				}
 			}
@@ -154,7 +164,7 @@
 	}
 
 	if !nmpkgs["golang.org/x/mobile/app"] {
-		return nil, fmt.Errorf(`%s does not import "golang.org/x/mobile/app"`, pkg.ImportPath)
+		return nil, fmt.Errorf(`%s does not import "golang.org/x/mobile/app"`, pkg.PkgPath)
 	}
 
 	return pkg, nil
diff --git a/cmd/gomobile/build_androidapp.go b/cmd/gomobile/build_androidapp.go
index 7b5e06f..1df9891 100644
--- a/cmd/gomobile/build_androidapp.go
+++ b/cmd/gomobile/build_androidapp.go
@@ -12,7 +12,6 @@
 	"encoding/xml"
 	"errors"
 	"fmt"
-	"go/build"
 	"io"
 	"io/ioutil"
 	"log"
@@ -22,16 +21,22 @@
 	"strings"
 
 	"golang.org/x/mobile/internal/binres"
+	"golang.org/x/tools/go/packages"
 )
 
-func goAndroidBuild(pkg *build.Package, androidArchs []string) (map[string]bool, error) {
+func goAndroidBuild(pkg *packages.Package, androidArchs []string) (map[string]bool, error) {
 	ndkRoot, err := ndkRoot()
 	if err != nil {
 		return nil, err
 	}
-	appName := path.Base(pkg.ImportPath)
+	appName := path.Base(pkg.PkgPath)
 	libName := androidPkgName(appName)
-	manifestPath := filepath.Join(pkg.Dir, "AndroidManifest.xml")
+
+	// 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.
+	dir := filepath.Dir(pkg.GoFiles[0])
+
+	manifestPath := filepath.Join(dir, "AndroidManifest.xml")
 	manifestData, err := ioutil.ReadFile(manifestPath)
 	if err != nil {
 		if !os.IsNotExist(err) {
@@ -72,7 +77,7 @@
 			return nil, err
 		}
 		err = goBuild(
-			pkg.ImportPath,
+			pkg.PkgPath,
 			env,
 			"-buildmode=c-shared",
 			"-o", libAbsPath,
@@ -97,7 +102,7 @@
 	}
 
 	if buildO == "" {
-		buildO = androidPkgName(filepath.Base(pkg.Dir)) + ".apk"
+		buildO = androidPkgName(path.Base(pkg.PkgPath)) + ".apk"
 	}
 	if !strings.HasSuffix(buildO, ".apk") {
 		return nil, fmt.Errorf("output file name %q does not end in '.apk'", buildO)
@@ -183,7 +188,7 @@
 	var arsc struct {
 		iconPath string
 	}
-	assetsDir := filepath.Join(pkg.Dir, "assets")
+	assetsDir := filepath.Join(dir, "assets")
 	assetsDirExists := true
 	fi, err := os.Stat(assetsDir)
 	if err != nil {
diff --git a/cmd/gomobile/build_iosapp.go b/cmd/gomobile/build_iosapp.go
index 9cc22ab..a0103de 100644
--- a/cmd/gomobile/build_iosapp.go
+++ b/cmd/gomobile/build_iosapp.go
@@ -9,7 +9,6 @@
 	"crypto/x509"
 	"encoding/pem"
 	"fmt"
-	"go/build"
 	"io/ioutil"
 	"os"
 	"os/exec"
@@ -17,15 +16,17 @@
 	"path/filepath"
 	"strings"
 	"text/template"
+
+	"golang.org/x/tools/go/packages"
 )
 
-func goIOSBuild(pkg *build.Package, bundleID string, archs []string) (map[string]bool, error) {
-	src := pkg.ImportPath
+func goIOSBuild(pkg *packages.Package, bundleID string, archs []string) (map[string]bool, error) {
+	src := pkg.PkgPath
 	if buildO != "" && !strings.HasSuffix(buildO, ".app") {
 		return nil, fmt.Errorf("-o must have an .app for -target=ios")
 	}
 
-	productName := rfc1034Label(path.Base(pkg.ImportPath))
+	productName := rfc1034Label(path.Base(pkg.PkgPath))
 	if productName == "" {
 		productName = "ProductName" // like xcode.
 	}
@@ -34,7 +35,7 @@
 	if err := infoplistTmpl.Execute(infoplist, infoplistTmplData{
 		// TODO: better bundle id.
 		BundleID: bundleID + "." + productName,
-		Name:     strings.Title(path.Base(pkg.ImportPath)),
+		Name:     strings.Title(path.Base(pkg.PkgPath)),
 	}); err != nil {
 		return nil, err
 	}
@@ -116,7 +117,7 @@
 
 	// TODO(jbd): Fallback to copying if renaming fails.
 	if buildO == "" {
-		n := pkg.ImportPath
+		n := pkg.PkgPath
 		if n == "." {
 			// use cwd name
 			cwd, err := os.Getwd()
@@ -176,13 +177,15 @@
 	return cert.Subject.OrganizationalUnit[0], nil
 }
 
-func iosCopyAssets(pkg *build.Package, xcodeProjDir string) error {
+func iosCopyAssets(pkg *packages.Package, xcodeProjDir string) error {
 	dstAssets := xcodeProjDir + "/main/assets"
 	if err := mkdir(dstAssets); err != nil {
 		return err
 	}
 
-	srcAssets := 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.
+	srcAssets := filepath.Join(filepath.Dir(pkg.GoFiles[0]), "assets")
 	fi, err := os.Stat(srcAssets)
 	if err != nil {
 		if os.IsNotExist(err) {
diff --git a/cmd/gomobile/env.go b/cmd/gomobile/env.go
index cb42d1c..d36a418 100644
--- a/cmd/gomobile/env.go
+++ b/cmd/gomobile/env.go
@@ -13,7 +13,6 @@
 
 // General mobile build environment. Initialized by envInit.
 var (
-	cwd          string
 	gomobilepath string // $GOPATH/pkg/gomobile
 
 	androidEnv map[string][]string // android arch -> []string
@@ -74,12 +73,6 @@
 }
 
 func envInit() (err error) {
-	// TODO(crawshaw): cwd only used by ctx.Import, which can take "."
-	cwd, err = os.Getwd()
-	if err != nil {
-		return err
-	}
-
 	// Setup the cross-compiler environments.
 	if ndkRoot, err := ndkRoot(); err == nil {
 		androidEnv = make(map[string][]string)
diff --git a/cmd/gomobile/install.go b/cmd/gomobile/install.go
index 05ff14b..beb7e52 100644
--- a/cmd/gomobile/install.go
+++ b/cmd/gomobile/install.go
@@ -8,7 +8,7 @@
 	"fmt"
 	"os"
 	"os/exec"
-	"path/filepath"
+	"path"
 	"strings"
 )
 
@@ -43,7 +43,7 @@
 		`adb`,
 		`install`,
 		`-r`,
-		androidPkgName(filepath.Base(pkg.Dir))+`.apk`,
+		androidPkgName(path.Base(pkg.PkgPath))+`.apk`,
 	)
 	c.Stdout = os.Stdout
 	c.Stderr = os.Stderr