cmd/gomobile: extend -target to accept {android,ios}/{arch} pairs

Not updated the doc yet.

Not useful for iOS yet.

For golang/go#10743

Change-Id: Iaffc41af2c876aa5889c44aae459241af9ec206e
Reviewed-on: https://go-review.googlesource.com/17580
Reviewed-by: David Crawshaw <crawshaw@golang.org>
diff --git a/cmd/gomobile/bind.go b/cmd/gomobile/bind.go
index 7641f63..340db72 100644
--- a/cmd/gomobile/bind.go
+++ b/cmd/gomobile/bind.go
@@ -70,16 +70,14 @@
 
 	args := cmd.flag.Args()
 
-	ctx.GOARCH = "arm"
-	switch buildTarget {
-	case "android":
-		ctx.GOOS = "android"
-	case "ios":
-		ctx.GOOS = "darwin"
-	default:
-		return fmt.Errorf(`unknown -target, %q.`, buildTarget)
+	targetOS, targetArchs, err := parseBuildTarget(buildTarget)
+	if err != nil {
+		return fmt.Errorf(`invalid -target=%q: %v`, buildTarget, err)
 	}
 
+	ctx.GOARCH = "arm"
+	ctx.GOOS = targetOS
+
 	if bindJavaPkg != "" && ctx.GOOS != "android" {
 		return fmt.Errorf("-javapkg is supported only for android target")
 	}
@@ -101,12 +99,12 @@
 
 	switch buildTarget {
 	case "android":
-		androidArchs := []string{"arm"}
-		return goAndroidBind(pkgs, androidArchs)
+		return goAndroidBind(pkgs, targetArchs)
 	case "ios":
+		// TODO: use targetArchs?
 		return goIOSBind(pkgs)
 	default:
-		return fmt.Errorf(`unknown -target, %q.`, buildTarget)
+		return fmt.Errorf(`invalid -target=%q`, buildTarget)
 	}
 }
 
diff --git a/cmd/gomobile/build.go b/cmd/gomobile/build.go
index 96f086a..87d0b8f 100644
--- a/cmd/gomobile/build.go
+++ b/cmd/gomobile/build.go
@@ -65,16 +65,14 @@
 
 	args := cmd.flag.Args()
 
-	ctx.GOARCH = "arm"
-	switch buildTarget {
-	case "android":
-		ctx.GOOS = "android"
-	case "ios":
-		ctx.GOOS = "darwin"
-	default:
-		return fmt.Errorf(`unknown -target, %q.`, buildTarget)
+	targetOS, targetArchs, err := parseBuildTarget(buildTarget)
+	if err != nil {
+		return fmt.Errorf(`invalid -target=%q: %v`, buildTarget, err)
 	}
 
+	ctx.GOARCH = targetArchs[0]
+	ctx.GOOS = targetOS
+
 	switch len(args) {
 	case 0:
 		pkg, err = ctx.ImportDir(cwd, build.ImportComment)
@@ -93,11 +91,10 @@
 	}
 
 	var nmpkgs map[string]bool
-	switch buildTarget {
+	switch targetOS {
 	case "android":
-		androidArchs := []string{"arm"}
 		if pkg.Name != "main" {
-			for _, arch := range androidArchs {
+			for _, arch := range targetArchs {
 				env := androidEnv[arch]
 				if err := goBuild(pkg.ImportPath, env); err != nil {
 					return err
@@ -105,11 +102,12 @@
 			}
 			return nil
 		}
-		nmpkgs, err = goAndroidBuild(pkg, androidArchs)
+		nmpkgs, err = goAndroidBuild(pkg, targetArchs)
 		if err != nil {
 			return err
 		}
 	case "ios":
+		// TODO: use targetArchs?
 		if runtime.GOOS != "darwin" {
 			return fmt.Errorf("-target=ios requires darwin host")
 		}
@@ -295,3 +293,72 @@
 	cmd.Env = append([]string{}, env...)
 	return runCmd(cmd)
 }
+
+func parseBuildTarget(buildTarget string) (os string, archs []string, _ error) {
+	if buildTarget == "" {
+		return "", nil, fmt.Errorf(`invalid target ""`)
+	}
+
+	all := false
+	archNames := []string{}
+	for i, p := range strings.Split(buildTarget, ",") {
+		osarch := strings.SplitN(p, "/", 2) // len(osarch) > 0
+		if osarch[0] != "android" && osarch[0] != "ios" {
+			return "", nil, fmt.Errorf(`unsupported os`)
+		}
+
+		if i == 0 {
+			os = osarch[0]
+		}
+
+		if os != osarch[0] {
+			return "", nil, fmt.Errorf(`cannot target different OSes`)
+		}
+
+		if len(osarch) == 1 {
+			all = true
+		} else {
+			archNames = append(archNames, osarch[1])
+		}
+	}
+
+	// verify all archs are supported one while deduping.
+	var supported []string
+	switch os {
+	case "ios":
+		supported = []string{"arm", "arm64", "amd64"}
+	case "android":
+		for arch, tc := range ndk {
+			if tc.minGoVer <= goVersion {
+				supported = append(supported, arch)
+			}
+		}
+	}
+
+	isSupported := func(arch string) bool {
+		for _, a := range supported {
+			if a == arch {
+				return true
+			}
+		}
+		return false
+	}
+
+	seen := map[string]bool{}
+	for _, arch := range archNames {
+		if _, ok := seen[arch]; ok {
+			continue
+		}
+		if !isSupported(arch) {
+			return "", nil, fmt.Errorf(`unsupported arch: %q`, arch)
+		}
+
+		seen[arch] = true
+		archs = append(archs, arch)
+	}
+
+	if all {
+		return os, supported, nil
+	}
+	return os, archs, nil
+}
diff --git a/cmd/gomobile/build_androidapp.go b/cmd/gomobile/build_androidapp.go
index d22d6af..ae951a5 100644
--- a/cmd/gomobile/build_androidapp.go
+++ b/cmd/gomobile/build_androidapp.go
@@ -227,10 +227,7 @@
 	}
 
 	// TODO: return nmpkgs
-	for _, v := range nmpkgs {
-		return v, nil // first value
-	}
-	return nil, nil
+	return nmpkgs[androidArchs[0]], 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 8ac96a8..5254f39 100644
--- a/cmd/gomobile/build_test.go
+++ b/cmd/gomobile/build_test.go
@@ -8,6 +8,7 @@
 	"bytes"
 	"os"
 	"path/filepath"
+	"strings"
 	"testing"
 	"text/template"
 )
@@ -101,3 +102,46 @@
 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
 `))
+
+func TestParseBuildTargetFlag(t *testing.T) {
+	androidArchs := "arm"
+	iosArchs := "arm,arm64,amd64"
+
+	tests := []struct {
+		in        string
+		wantErr   bool
+		wantOS    string
+		wantArchs string
+	}{
+		{"android", false, "android", androidArchs},
+		{"android,android/arm", false, "android", androidArchs},
+		{"android/arm", false, "android", "arm"},
+
+		{"ios", false, "ios", iosArchs},
+		{"ios,ios/arm", false, "ios", iosArchs},
+		{"ios/arm", false, "ios", "arm"},
+		{"ios/amd64", false, "ios", "amd64"},
+
+		{"", true, "", ""},
+		{"linux", true, "", ""},
+		{"android/x86", true, "", ""},
+		{"android/arm5", true, "", ""},
+		{"ios/mips", true, "", ""},
+		{"android,ios", true, "", ""},
+		{"ios,android", true, "", ""},
+	}
+
+	for _, tc := range tests {
+		gotOS, gotArchs, err := parseBuildTarget(tc.in)
+		if tc.wantErr {
+			if err == nil {
+				t.Errorf("-target=%q; want error, got (%q, %q, nil)", tc.in, gotOS, gotArchs)
+			}
+			continue
+		}
+		if err != nil || gotOS != tc.wantOS || strings.Join(gotArchs, ",") != tc.wantArchs {
+			t.Errorf("-target=%q; want (%v, [%v], nil), got (%q, %q, %v)",
+				tc.in, tc.wantOS, tc.wantArchs, gotOS, gotArchs, err)
+		}
+	}
+}
diff --git a/cmd/gomobile/install.go b/cmd/gomobile/install.go
index d001018..522bd3d 100644
--- a/cmd/gomobile/install.go
+++ b/cmd/gomobile/install.go
@@ -30,7 +30,7 @@
 }
 
 func runInstall(cmd *command) error {
-	if buildTarget != "android" {
+	if !strings.HasPrefix(buildTarget, "android") {
 		return fmt.Errorf("install is not supported for -target=%s", buildTarget)
 	}
 	if err := runBuild(cmd); err != nil {