cmd/gomobile: don't run gobind with the ios tag

Bindings are independent of any particular GOOS/GOARCH pair and
as such the gomobile bind command doesn't set GOOS nor GOARCH when
running gobind. However, the ios tag was still added to the list
of tags to pass to gobind for -target=ios.

Move the ios tag to when actually building the bound packages,
mirroring gomobile build.

Add TestBindIOS and update TestBindAndroid.

Updates golang/go#24644

Change-Id: I007829c26036427a3376bba11a1ccb86e7338848
Reviewed-on: https://go-review.googlesource.com/104458
Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
diff --git a/cmd/gomobile/bind.go b/cmd/gomobile/bind.go
index adbf66e..0d22d7d 100644
--- a/cmd/gomobile/bind.go
+++ b/cmd/gomobile/bind.go
@@ -79,13 +79,13 @@
 		return fmt.Errorf(`invalid -target=%q: %v`, buildTarget, err)
 	}
 
+	oldCtx := ctx
+	defer func() {
+		ctx = oldCtx
+	}()
 	ctx.GOARCH = "arm"
 	ctx.GOOS = targetOS
 
-	if ctx.GOOS == "darwin" {
-		ctx.BuildTags = append(ctx.BuildTags, "ios")
-	}
-
 	if bindJavaPkg != "" && ctx.GOOS != "android" {
 		return fmt.Errorf("-javapkg is supported only for android target")
 	}
diff --git a/cmd/gomobile/bind_iosapp.go b/cmd/gomobile/bind_iosapp.go
index 6d95def..f8c4fdf 100644
--- a/cmd/gomobile/bind_iosapp.go
+++ b/cmd/gomobile/bind_iosapp.go
@@ -8,7 +8,6 @@
 	"fmt"
 	"go/build"
 	"io"
-	"io/ioutil"
 	"os/exec"
 	"path/filepath"
 	"strings"
@@ -35,6 +34,8 @@
 		return err
 	}
 
+	ctx.BuildTags = append(ctx.BuildTags, "ios")
+
 	srcDir := filepath.Join(tmpdir, "src", "gobind")
 	gopath := fmt.Sprintf("GOPATH=%s%c%s", tmpdir, filepath.ListSeparator, goEnv("GOPATH"))
 
@@ -134,7 +135,11 @@
 	if err := symlink("Versions/Current/Resources", buildO+"/Resources"); err != nil {
 		return err
 	}
-	if err := ioutil.WriteFile(buildO+"/Resources/Info.plist", []byte(iosBindInfoPlist), 0666); err != nil {
+	err := writeFile(buildO+"/Resources/Info.plist", func(w io.Writer) error {
+		_, err := w.Write([]byte(iosBindInfoPlist))
+		return err
+	})
+	if err != nil {
 		return err
 	}
 
@@ -145,7 +150,7 @@
 		Module:  title,
 		Headers: headerFiles,
 	}
-	err := writeFile(buildO+"/Versions/A/Modules/module.modulemap", func(w io.Writer) error {
+	err = writeFile(buildO+"/Versions/A/Modules/module.modulemap", func(w io.Writer) error {
 		return iosModuleMapTmpl.Execute(w, mmVals)
 	})
 	if err != nil {
diff --git a/cmd/gomobile/bind_test.go b/cmd/gomobile/bind_test.go
index 857c802..1825666 100644
--- a/cmd/gomobile/bind_test.go
+++ b/cmd/gomobile/bind_test.go
@@ -14,8 +14,6 @@
 	"text/template"
 )
 
-// TODO(crawshaw): TestBindIOS
-
 func TestImportPackagesPathCleaning(t *testing.T) {
 	slashPath := "golang.org/x/mobile/example/bind/hello/"
 	pkgs, err := importPackages([]string{slashPath})
@@ -45,6 +43,7 @@
 		buildX = false
 		buildO = ""
 		buildTarget = ""
+		bindJavaPkg = ""
 	}()
 	buildN = true
 	buildX = true
@@ -52,15 +51,13 @@
 	buildTarget = "android/arm"
 
 	tests := []struct {
-		javaPkg    string
-		wantGobind string
+		javaPkg string
 	}{
 		{
-			wantGobind: "gobind -lang=java",
+			// Empty javaPkg
 		},
 		{
-			javaPkg:    "com.example.foo",
-			wantGobind: "gobind -lang=java -javapkg=com.example.foo",
+			javaPkg: "com.example.foo",
 		},
 	}
 	for _, tc := range tests {
@@ -83,12 +80,10 @@
 		data := struct {
 			outputData
 			AndroidPlatform string
-			GobindJavaCmd   string
 			JavaPkg         string
 		}{
 			outputData:      defaultOutputData(),
 			AndroidPlatform: platform,
-			GobindJavaCmd:   tc.wantGobind,
 			JavaPkg:         tc.javaPkg,
 		}
 
@@ -109,6 +104,75 @@
 	}
 }
 
+func TestBindIOS(t *testing.T) {
+	if !xcodeAvailable() {
+		t.Skip("Xcode is missing")
+	}
+	defer func() {
+		xout = os.Stderr
+		buildN = false
+		buildX = false
+		buildO = ""
+		buildTarget = ""
+		bindPrefix = ""
+	}()
+	buildN = true
+	buildX = true
+	buildO = "Asset.framework"
+	buildTarget = "ios/arm"
+
+	tests := []struct {
+		prefix string
+	}{
+		{
+			// empty prefix
+		},
+		{
+			prefix: "Foo",
+		},
+	}
+	for _, tc := range tests {
+		bindPrefix = tc.prefix
+
+		buf := new(bytes.Buffer)
+		xout = buf
+		gopath = filepath.SplitList(goEnv("GOPATH"))[0]
+		if goos == "windows" {
+			os.Setenv("HOMEDRIVE", "C:")
+		}
+		cmdBind.flag.Parse([]string{"golang.org/x/mobile/asset"})
+		err := runBind(cmdBind)
+		if err != nil {
+			t.Log(buf.String())
+			t.Fatal(err)
+		}
+		got := filepath.ToSlash(buf.String())
+
+		data := struct {
+			outputData
+			Prefix string
+		}{
+			outputData: defaultOutputData(),
+			Prefix:     tc.prefix,
+		}
+
+		wantBuf := new(bytes.Buffer)
+		if err := bindIOSTmpl.Execute(wantBuf, data); err != nil {
+			t.Errorf("%+v: computing diff failed: %v", tc, err)
+			continue
+		}
+
+		diff, err := diff(got, wantBuf.String())
+		if err != nil {
+			t.Errorf("%+v: computing diff failed: %v", tc, err)
+			continue
+		}
+		if diff != "" {
+			t.Errorf("%+v: unexpected output:\n%s", tc, diff)
+		}
+	}
+}
+
 var bindAndroidTmpl = template.Must(template.New("output").Parse(`GOMOBILE={{.GOPATH}}/pkg/gomobile
 WORK=$WORK
 gobind -lang=go,java -outdir=$WORK{{if .JavaPkg}} -javapkg={{.JavaPkg}}{{end}} golang.org/x/mobile/asset
@@ -116,3 +180,27 @@
 PWD=$WORK/java javac -d $WORK/javac-output -source 1.7 -target 1.7 -bootclasspath {{.AndroidPlatform}}/android.jar *.java
 jar c -C $WORK/javac-output .
 `))
+
+var bindIOSTmpl = template.Must(template.New("output").Parse(`GOMOBILE={{.GOPATH}}/pkg/gomobile
+WORK=$WORK
+gobind -lang=go,objc -outdir=$WORK{{if .Prefix}} -prefix={{.Prefix}}{{end}} golang.org/x/mobile/asset
+GOARM=7 GOOS=darwin GOARCH=arm CC=clang-iphoneos CXX=clang-iphoneos CGO_CFLAGS=-isysroot=iphoneos -miphoneos-version-min=6.1 -arch armv7 CGO_LDFLAGS=-isysroot=iphoneos -miphoneos-version-min=6.1 -arch armv7 CGO_ENABLED=1 GOPATH=$WORK:$GOPATH go build -tags ios -x -buildmode=c-archive -o $WORK/asset-arm.a gobind
+rm -r -f "Asset.framework"
+mkdir -p Asset.framework/Versions/A/Headers
+ln -s A Asset.framework/Versions/Current
+ln -s Versions/Current/Headers Asset.framework/Headers
+ln -s Versions/Current/Asset Asset.framework/Asset
+xcrun lipo -create -arch armv7 $WORK/asset-arm.a -o Asset.framework/Versions/A/Asset
+cp $WORK/src/gobind/{{.Prefix}}Asset.objc.h Asset.framework/Versions/A/Headers/{{.Prefix}}Asset.objc.h
+mkdir -p Asset.framework/Versions/A/Headers
+cp $WORK/src/gobind/universe.objc.h Asset.framework/Versions/A/Headers/universe.objc.h
+mkdir -p Asset.framework/Versions/A/Headers
+cp $WORK/src/gobind/ref.h Asset.framework/Versions/A/Headers/ref.h
+mkdir -p Asset.framework/Versions/A/Headers
+mkdir -p Asset.framework/Versions/A/Headers
+mkdir -p Asset.framework/Versions/A/Resources
+ln -s Versions/Current/Resources Asset.framework/Resources
+mkdir -p Asset.framework/Resources
+mkdir -p Asset.framework/Versions/A/Modules
+ln -s Versions/Current/Modules Asset.framework/Modules
+`))
diff --git a/cmd/gomobile/build.go b/cmd/gomobile/build.go
index bdd3fe0..156feb4 100644
--- a/cmd/gomobile/build.go
+++ b/cmd/gomobile/build.go
@@ -74,6 +74,10 @@
 		return fmt.Errorf(`invalid -target=%q: %v`, buildTarget, err)
 	}
 
+	oldCtx := ctx
+	defer func() {
+		ctx = oldCtx
+	}()
 	ctx.GOARCH = targetArchs[0]
 	ctx.GOOS = targetOS