cmd/gomobile: update init/bind/build for multiple android archs

init command installs std for all the architectures supported by the
current go tool version (as listed in androidEnv).

build and bind commands pass the list of architectures to the underlying
functions. The list is currently hard-coded []string{"arm"}. In a
separate CL, the list will be populated from the -target flag value.

Still targets arm devices only.

For golang/go#10743

Change-Id: I62b5899859e76ad78a2dc55111e87aa13a68a1f9
Reviewed-on: https://go-review.googlesource.com/17749
Reviewed-by: David Crawshaw <crawshaw@golang.org>
diff --git a/cmd/gomobile/bind.go b/cmd/gomobile/bind.go
index a4f7ecf..7641f63 100644
--- a/cmd/gomobile/bind.go
+++ b/cmd/gomobile/bind.go
@@ -101,7 +101,8 @@
 
 	switch buildTarget {
 	case "android":
-		return goAndroidBind(pkgs)
+		androidArchs := []string{"arm"}
+		return goAndroidBind(pkgs, androidArchs)
 	case "ios":
 		return goIOSBind(pkgs)
 	default:
@@ -289,6 +290,8 @@
 		return nil, err
 	}
 
+	goos, goarch := getenv(env, "GOOS"), getenv(env, "GOARCH")
+
 	// Assemble a fake GOPATH and trick go/importer into using it.
 	// Ideally the importer package would let us provide this to
 	// it somehow, but this works with what's in Go 1.5 today and
@@ -305,7 +308,7 @@
 	for i, p := range pkgs {
 		importPath := p.ImportPath
 		src := filepath.Join(pkgdir(env), importPath+".a")
-		dst := filepath.Join(fakegopath, "pkg/"+getenv(env, "GOOS")+"_"+getenv(env, "GOARCH")+"/"+importPath+".a")
+		dst := filepath.Join(fakegopath, "pkg/"+goos+"_"+goarch+"/"+importPath+".a")
 		if err := copyFile(dst, src); err != nil {
 			return nil, err
 		}
@@ -315,6 +318,7 @@
 		}
 		oldDefault := build.Default
 		build.Default = ctx // copy
+		build.Default.GOARCH = goarch
 		build.Default.GOPATH = fakegopath
 		p, err := importer.Default().Import(importPath)
 		build.Default = oldDefault
diff --git a/cmd/gomobile/bind_androidapp.go b/cmd/gomobile/bind_androidapp.go
index c33c05f..e2e7a9f 100644
--- a/cmd/gomobile/bind_androidapp.go
+++ b/cmd/gomobile/bind_androidapp.go
@@ -18,7 +18,7 @@
 	"text/template"
 )
 
-func goAndroidBind(pkgs []*build.Package) error {
+func goAndroidBind(pkgs []*build.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)")
 	}
@@ -28,75 +28,106 @@
 		// https://golang.org/issue/13234.
 		androidArgs = []string{"-gcflags=-shared", "-ldflags=-shared"}
 	}
-	typesPkgs, err := loadExportData(pkgs, androidEnv["arm"], androidArgs...)
-	if err != nil {
-		return err
-	}
 
-	binder, err := newBinder(typesPkgs)
-	if err != nil {
-		return err
-	}
-
-	for _, pkg := range typesPkgs {
-		if err := binder.GenGo(pkg, tmpdir); err != nil {
-			return err
-		}
-	}
-
-	mainFile := filepath.Join(tmpdir, "androidlib/main.go")
-	err = writeFile(mainFile, func(w io.Writer) error {
-		return androidMainTmpl.Execute(w, binder.pkgs)
-	})
-	if err != nil {
-		return fmt.Errorf("failed to create the main package for android: %v", err)
+	paths := make([]string, len(pkgs))
+	for i, p := range pkgs {
+		paths[i] = p.ImportPath
 	}
 
 	androidDir := filepath.Join(tmpdir, "android")
+	mainFile := filepath.Join(tmpdir, "androidlib/main.go")
 
-	err = goBuild(
-		mainFile,
-		androidEnv["arm"],
-		"-buildmode=c-shared",
-		"-o="+filepath.Join(androidDir, "src/main/jniLibs/armeabi-v7a/libgojni.so"),
-	)
-	if err != nil {
-		return err
-	}
+	// Generate binding code and java source code only when processing the first package.
+	first := true
+	for _, arch := range androidArchs {
+		env := androidEnv[arch]
+		toolchain := ndk.Toolchain(arch)
 
-	p, err := ctx.Import("golang.org/x/mobile/bind", cwd, build.ImportComment)
-	if err != nil {
-		return fmt.Errorf(`"golang.org/x/mobile/bind" is not found; run go get golang.org/x/mobile/bind`)
-	}
-	repo := filepath.Clean(filepath.Join(p.Dir, "..")) // golang.org/x/mobile directory.
+		if !first {
+			if err := goInstall(paths, env, androidArgs...); err != nil {
+				return err
+			}
+			err := goBuild(
+				mainFile,
+				env,
+				"-buildmode=c-shared",
+				"-o="+filepath.Join(androidDir, "src/main/jniLibs/"+toolchain.abi+"/libgojni.so"),
+			)
+			if err != nil {
+				return err
+			}
 
-	for _, pkg := range binder.pkgs {
-		pkgpath := strings.Replace(bindJavaPkg, ".", "/", -1)
-		if bindJavaPkg == "" {
-			pkgpath = "go/" + pkg.Name()
+			continue
 		}
-		if err := binder.GenJava(pkg, filepath.Join(androidDir, "src/main/java/"+pkgpath)); err != nil {
+		first = false
+
+		typesPkgs, err := loadExportData(pkgs, env, androidArgs...)
+		if err != nil {
+			return fmt.Errorf("loadExportData failed %v", err)
+		}
+
+		binder, err := newBinder(typesPkgs)
+		if err != nil {
+			return err
+		}
+
+		for _, pkg := range typesPkgs {
+			if err := binder.GenGo(pkg, tmpdir); err != nil {
+				return err
+			}
+		}
+
+		err = writeFile(mainFile, func(w io.Writer) error {
+			return androidMainTmpl.Execute(w, binder.pkgs)
+		})
+		if err != nil {
+			return fmt.Errorf("failed to create the main package for android: %v", err)
+		}
+
+		err = goBuild(
+			mainFile,
+			env,
+			"-buildmode=c-shared",
+			"-o="+filepath.Join(androidDir, "src/main/jniLibs/"+toolchain.abi+"/libgojni.so"),
+		)
+		if err != nil {
+			return err
+		}
+
+		p, err := ctx.Import("golang.org/x/mobile/bind", cwd, build.ImportComment)
+		if err != nil {
+			return fmt.Errorf(`"golang.org/x/mobile/bind" is not found; run go get golang.org/x/mobile/bind`)
+		}
+		repo := filepath.Clean(filepath.Join(p.Dir, "..")) // golang.org/x/mobile directory.
+
+		for _, pkg := range binder.pkgs {
+			pkgpath := strings.Replace(bindJavaPkg, ".", "/", -1)
+			if bindJavaPkg == "" {
+				pkgpath = "go/" + pkg.Name()
+			}
+			if err := binder.GenJava(pkg, filepath.Join(androidDir, "src/main/java/"+pkgpath)); err != nil {
+				return err
+			}
+		}
+
+		dst := filepath.Join(androidDir, "src/main/java/go/LoadJNI.java")
+		genLoadJNI := func(w io.Writer) error {
+			_, err := io.WriteString(w, loadSrc)
+			return err
+		}
+		if err := writeFile(dst, genLoadJNI); err != nil {
+			return err
+		}
+
+		src := filepath.Join(repo, "bind/java/Seq.java")
+		dst = filepath.Join(androidDir, "src/main/java/go/Seq.java")
+		rm(dst)
+		if err := symlink(src, dst); err != nil {
 			return err
 		}
 	}
 
-	dst := filepath.Join(androidDir, "src/main/java/go/LoadJNI.java")
-	genLoadJNI := func(w io.Writer) error {
-		_, err := io.WriteString(w, loadSrc)
-		return err
-	}
-	if err := writeFile(dst, genLoadJNI); err != nil {
-		return err
-	}
-
-	src := filepath.Join(repo, "bind/java/Seq.java")
-	dst = filepath.Join(androidDir, "src/main/java/go/Seq.java")
-	rm(dst)
-	if err := symlink(src, dst); err != nil {
-		return err
-	}
-
-	return buildAAR(androidDir, pkgs)
+	return buildAAR(androidDir, pkgs, androidArchs)
 }
 
 var loadSrc = `package go;
@@ -158,7 +189,7 @@
 //	aidl (optional, not relevant)
 //
 // javac and jar commands are needed to build classes.jar.
-func buildAAR(androidDir string, pkgs []*build.Package) (err error) {
+func buildAAR(androidDir string, pkgs []*build.Package, androidArchs []string) (err error) {
 	var out io.Writer = ioutil.Discard
 	if buildO == "" {
 		buildO = pkgs[0].Name + ".aar"
@@ -252,19 +283,22 @@
 		}
 	}
 
-	lib := "armeabi-v7a/libgojni.so"
-	w, err = aarwcreate("jni/" + lib)
-	if err != nil {
-		return err
-	}
-	if !buildN {
-		r, err := os.Open(filepath.Join(androidDir, "src/main/jniLibs/"+lib))
+	for _, arch := range androidArchs {
+		toolchain := ndk.Toolchain(arch)
+		lib := toolchain.abi + "/libgojni.so"
+		w, err = aarwcreate("jni/" + lib)
 		if err != nil {
 			return err
 		}
-		defer r.Close()
-		if _, err := io.Copy(w, r); err != nil {
-			return err
+		if !buildN {
+			r, err := os.Open(filepath.Join(androidDir, "src/main/jniLibs/"+lib))
+			if err != nil {
+				return err
+			}
+			defer r.Close()
+			if _, err := io.Copy(w, r); err != nil {
+				return err
+			}
 		}
 	}
 
diff --git a/cmd/gomobile/build.go b/cmd/gomobile/build.go
index 0633ccf..96f086a 100644
--- a/cmd/gomobile/build.go
+++ b/cmd/gomobile/build.go
@@ -95,10 +95,17 @@
 	var nmpkgs map[string]bool
 	switch buildTarget {
 	case "android":
+		androidArchs := []string{"arm"}
 		if pkg.Name != "main" {
-			return goBuild(pkg.ImportPath, androidEnv["arm"])
+			for _, arch := range androidArchs {
+				env := androidEnv[arch]
+				if err := goBuild(pkg.ImportPath, env); err != nil {
+					return err
+				}
+			}
+			return nil
 		}
-		nmpkgs, err = goAndroidBuild(pkg)
+		nmpkgs, err = goAndroidBuild(pkg, androidArchs)
 		if err != nil {
 			return err
 		}
diff --git a/cmd/gomobile/build_androidapp.go b/cmd/gomobile/build_androidapp.go
index ddd2e7b..37efe2b 100644
--- a/cmd/gomobile/build_androidapp.go
+++ b/cmd/gomobile/build_androidapp.go
@@ -21,7 +21,7 @@
 	"strings"
 )
 
-func goAndroidBuild(pkg *build.Package) (map[string]bool, error) {
+func goAndroidBuild(pkg *build.Package, androidArchs []string) (map[string]bool, error) {
 	appName := path.Base(pkg.ImportPath)
 	libName := androidPkgName(appName)
 	manifestPath := filepath.Join(pkg.Dir, "AndroidManifest.xml")
@@ -53,22 +53,31 @@
 		}
 	}
 
-	toolchain := ndk.Toolchain("arm")
+	libFiles := []string{}
+	nmpkgs := make(map[string]map[string]bool) // map: goarch -> extractPkgs' output
 
-	libPath := filepath.Join(tmpdir, "lib"+libName+".so")
-	err = goBuild(
-		pkg.ImportPath,
-		androidEnv["arm"],
-		"-buildmode=c-shared",
-		"-o", libPath,
-	)
-	if err != nil {
-		return nil, err
-	}
-
-	nmpkgs, err := extractPkgs(toolchain.Path("nm"), libPath)
-	if err != nil {
-		return nil, err
+	for _, arch := range androidArchs {
+		env := androidEnv[arch]
+		toolchain := ndk.Toolchain(arch)
+		libPath := "lib/" + toolchain.abi + "/lib" + libName + ".so"
+		libAbsPath := filepath.Join(tmpdir, libPath)
+		if err := mkdir(filepath.Dir(libAbsPath)); err != nil {
+			return nil, err
+		}
+		err = goBuild(
+			pkg.ImportPath,
+			env,
+			"-buildmode=c-shared",
+			"-o", libAbsPath,
+		)
+		if err != nil {
+			return nil, err
+		}
+		nmpkgs[arch], err = extractPkgs(toolchain.Path("nm"), libAbsPath)
+		if err != nil {
+			return nil, err
+		}
+		libFiles = append(libFiles, libPath)
 	}
 
 	block, _ := pem.Decode([]byte(debugCert))
@@ -151,15 +160,23 @@
 		return nil, err
 	}
 
-	if err := apkwWriteFile("lib/armeabi/lib"+libName+".so", libPath); err != nil {
-		return nil, err
+	for _, libFile := range libFiles {
+		if err := apkwWriteFile(libFile, filepath.Join(tmpdir, libFile)); err != nil {
+			return nil, err
+		}
 	}
 
-	if nmpkgs["golang.org/x/mobile/exp/audio/al"] {
-		dst := "lib/armeabi/libopenal.so"
-		src := filepath.Join(ndk.Root(), "openal/"+dst)
-		if err := apkwWriteFile(dst, src); err != nil {
-			return nil, err
+	for _, arch := range androidArchs {
+		toolchain := ndk.Toolchain(arch)
+		if nmpkgs[goarch]["golang.org/x/mobile/exp/audio/al"] {
+			dst := "lib/" + toolchain.arch + "/libopenal.so"
+			if arch == "arm" {
+				dst = "lib/armeabi/libopenal.so"
+			}
+			src := filepath.Join(ndk.Root(), "openal/"+dst)
+			if err := apkwWriteFile(dst, src); err != nil {
+				return nil, err
+			}
 		}
 	}
 
@@ -209,7 +226,11 @@
 		}
 	}
 
-	return nmpkgs, nil
+	// TODO: return nmpkgs
+	for _, v := range nmpkgs {
+		return v, nil // first value
+	}
+	return nil, nil
 }
 
 // androidPkgName sanitizes the go package name to be acceptable as a android
diff --git a/cmd/gomobile/build_test.go b/cmd/gomobile/build_test.go
index 83b27f9..8ac96a8 100644
--- a/cmd/gomobile/build_test.go
+++ b/cmd/gomobile/build_test.go
@@ -98,5 +98,6 @@
 
 var androidBuildTmpl = template.Must(template.New("output").Parse(`GOMOBILE={{.GOPATH}}/pkg/gomobile
 WORK=$WORK
-GOOS=android GOARCH=arm CC=$GOMOBILE/android-{{.NDK}}/arm/bin/arm-linux-androideabi-gcc{{.EXE}} CXX=$GOMOBILE/android-{{.NDK}}/arm/bin/arm-linux-androideabi-g++{{.EXE}} CGO_ENABLED=1 GOARM=7 go build -p={{.NumCPU}} -pkgdir=$GOMOBILE/pkg_android_arm -tags="tag1" -x -buildmode=c-shared -o $WORK/libbasic.so golang.org/x/mobile/example/basic
+mkdir -p $WORK/lib/armeabi-v7a
+GOOS=android GOARCH=arm CC=$GOMOBILE/android-{{.NDK}}/arm/bin/arm-linux-androideabi-gcc{{.EXE}} CXX=$GOMOBILE/android-{{.NDK}}/arm/bin/arm-linux-androideabi-g++{{.EXE}} CGO_ENABLED=1 GOARM=7 go build -p={{.NumCPU}} -pkgdir=$GOMOBILE/pkg_android_arm -tags="tag1" -x -buildmode=c-shared -o $WORK/lib/armeabi-v7a/libbasic.so golang.org/x/mobile/example/basic
 `))
diff --git a/cmd/gomobile/init.go b/cmd/gomobile/init.go
index 9e3a5be..1eed2e5 100644
--- a/cmd/gomobile/init.go
+++ b/cmd/gomobile/init.go
@@ -112,6 +112,10 @@
 		removeAll(tmpdir)
 	}()
 
+	if err := envInit(); err != nil {
+		return err
+	}
+
 	if err := fetchNDK(); err != nil {
 		return err
 	}
@@ -119,10 +123,6 @@
 		return err
 	}
 
-	if err := envInit(); err != nil {
-		return err
-	}
-
 	if runtime.GOOS == "darwin" {
 		// Install common x/mobile packages for local development.
 		// These are often slow to compile (due to cgo) and easy to forget.
@@ -146,9 +146,12 @@
 		// https://golang.org/issue/13234.
 		androidArgs = []string{"-gcflags=-shared", "-ldflags=-shared"}
 	}
-	if err := installStd(androidEnv["arm"], androidArgs...); err != nil {
-		return err
+	for _, env := range androidEnv {
+		if err := installStd(env, androidArgs...); err != nil {
+			return err
+		}
 	}
+
 	if err := installDarwin(); err != nil {
 		return err
 	}
@@ -310,10 +313,19 @@
 		resetReadOnlyFlagAll(filepath.Join(tmpdir, "openal"))
 	}
 	ndkroot := ndk.Root()
-	dst := filepath.Join(ndkroot, "arm", "sysroot", "usr", "include")
-	src := filepath.Join(tmpdir, "openal", "include")
-	if err := move(dst, src, "AL"); err != nil {
-		return err
+	src := filepath.Join(tmpdir, "openal/include/AL")
+	for arch := range androidEnv {
+		toolchain := ndk.Toolchain(arch)
+		dst := filepath.Join(ndkroot, toolchain.arch+"/sysroot/usr/include/AL")
+		if buildX || buildN {
+			printcmd("cp -r %s %s", src, dst)
+		}
+		if buildN {
+			continue
+		}
+		if err := doCopyAll(dst, src); err != nil {
+			return err
+		}
 	}
 	libDst := filepath.Join(ndkroot, "openal")
 	libSrc := filepath.Join(tmpdir, "openal")
@@ -389,38 +401,44 @@
 		resetReadOnlyFlagAll(filepath.Join(tmpdir, "android-"+ndkVersion))
 	}
 
-	dst := filepath.Join(ndk.Root(), "arm")
-	dstSysroot := filepath.Join(dst, "sysroot/usr")
-	if err := mkdir(dstSysroot); err != nil {
-		return err
-	}
-
-	srcSysroot := filepath.Join(tmpdir, "android-"+ndkVersion+"/platforms/android-15/arch-arm/usr")
-	if err := move(dstSysroot, srcSysroot, "include", "lib"); err != nil {
-		return err
-	}
-
-	ndkpath := filepath.Join(tmpdir, "android-"+ndkVersion+"/toolchains/arm-linux-androideabi-4.8/prebuilt")
-	if goos == "windows" && ndkarch == "x86" {
-		ndkpath = filepath.Join(ndkpath, "windows")
-	} else {
-		ndkpath = filepath.Join(ndkpath, goos+"-"+ndkarch)
-	}
-	if err := move(dst, ndkpath, "bin", "lib", "libexec"); err != nil {
-		return err
-	}
-
-	linkpath := filepath.Join(dst, "arm-linux-androideabi/bin")
-	if err := mkdir(linkpath); err != nil {
-		return err
-	}
-	for _, name := range []string{"ld", "as", "gcc", "g++"} {
-		if goos == "windows" {
-			name += ".exe"
-		}
-		if err := symlink(filepath.Join(dst, "bin", "arm-linux-androideabi-"+name), filepath.Join(linkpath, name)); err != nil {
+	for arch := range androidEnv {
+		toolchain := ndk.Toolchain(arch)
+		dst := filepath.Join(ndk.Root(), toolchain.arch)
+		dstSysroot := filepath.Join(dst, "sysroot")
+		if err := mkdir(dstSysroot); err != nil {
 			return err
 		}
+
+		srcSysroot := filepath.Join(tmpdir, fmt.Sprintf(
+			"android-%s/platforms/%s/arch-%s", ndkVersion, toolchain.platform, toolchain.arch))
+		if err := move(dstSysroot, srcSysroot, "usr"); err != nil {
+			return err
+		}
+
+		ndkpath := filepath.Join(tmpdir, fmt.Sprintf(
+			"android-%s/toolchains/%s/prebuilt", ndkVersion, toolchain.gcc))
+		if goos == "windows" && ndkarch == "x86" {
+			ndkpath = filepath.Join(ndkpath, "windows")
+		} else {
+			ndkpath = filepath.Join(ndkpath, goos+"-"+ndkarch)
+		}
+		if err := move(dst, ndkpath, "bin", "lib", "libexec"); err != nil {
+			return err
+		}
+
+		linkpath := filepath.Join(dst, toolchain.toolPrefix+"/bin")
+		if err := mkdir(linkpath); err != nil {
+			return err
+		}
+
+		for _, name := range []string{"ld", "as", "gcc", "g++"} {
+			if goos == "windows" {
+				name += ".exe"
+			}
+			if err := symlink(filepath.Join(dst, "bin", toolchain.toolPrefix+"-"+name), filepath.Join(linkpath, name)); err != nil {
+				return err
+			}
+		}
 	}
 	return nil
 }
diff --git a/cmd/gomobile/init_test.go b/cmd/gomobile/init_test.go
index ba46993..b9fbb69 100644
--- a/cmd/gomobile/init_test.go
+++ b/cmd/gomobile/init_test.go
@@ -45,6 +45,7 @@
 		os.Setenv("HOMEDRIVE", "C:")
 	}
 
+	// TODO(hyangah): test with go1_6.
 	err := runInit(cmdInit)
 	if err != nil {
 		t.Log(buf.String())
@@ -112,9 +113,8 @@
 mkdir -p $GOMOBILE/dl
 curl -o$GOMOBILE/dl/gomobile-{{.NDK}}-{{.GOOS}}-{{.NDKARCH}}.tar.gz https://dl.google.com/go/mobile/gomobile-{{.NDK}}-{{.GOOS}}-{{.NDKARCH}}.tar.gz
 tar xfz $GOMOBILE/dl/gomobile-{{.NDK}}-{{.GOOS}}-{{.NDKARCH}}.tar.gz
-mkdir -p $GOMOBILE/android-{{.NDK}}/arm/sysroot/usr
-mv $WORK/android-{{.NDK}}/platforms/android-15/arch-arm/usr/include $GOMOBILE/android-{{.NDK}}/arm/sysroot/usr/include
-mv $WORK/android-{{.NDK}}/platforms/android-15/arch-arm/usr/lib $GOMOBILE/android-{{.NDK}}/arm/sysroot/usr/lib
+mkdir -p $GOMOBILE/android-{{.NDK}}/arm/sysroot
+mv $WORK/android-{{.NDK}}/platforms/android-15/arch-arm/usr $GOMOBILE/android-{{.NDK}}/arm/sysroot/usr
 mv $WORK/android-{{.NDK}}/toolchains/arm-linux-androideabi-4.8/prebuilt/{{.GOOS}}-{{.NDKARCH}}/bin $GOMOBILE/android-{{.NDK}}/arm/bin
 mv $WORK/android-{{.NDK}}/toolchains/arm-linux-androideabi-4.8/prebuilt/{{.GOOS}}-{{.NDKARCH}}/lib $GOMOBILE/android-{{.NDK}}/arm/lib
 mv $WORK/android-{{.NDK}}/toolchains/arm-linux-androideabi-4.8/prebuilt/{{.GOOS}}-{{.NDKARCH}}/libexec $GOMOBILE/android-{{.NDK}}/arm/libexec
@@ -126,7 +126,7 @@
 mkdir -p $GOMOBILE/dl
 curl -o$GOMOBILE/dl/gomobile-openal-soft-1.16.0.1.tar.gz https://dl.google.com/go/mobile/gomobile-openal-soft-1.16.0.1.tar.gz
 tar xfz $GOMOBILE/dl/gomobile-openal-soft-1.16.0.1.tar.gz
-mv $WORK/openal/include/AL $GOMOBILE/android-{{.NDK}}/arm/sysroot/usr/include/AL
+cp -r $WORK/openal/include/AL $GOMOBILE/android-{{.NDK}}/arm/sysroot/usr/include/AL
 mkdir -p $GOMOBILE/android-{{.NDK}}/openal
 mv $WORK/openal/lib $GOMOBILE/android-{{.NDK}}/openal/lib{{if eq .GOOS "darwin"}}
 go install -p={{.NumCPU}} -x golang.org/x/mobile/gl