cmd/gomobile: concurrent build for iOS archive files

This change makes building archive files for iOS concurrent for each
architecture and each platform. The strategy is basically the same as
my previous CL for Android: https://go.dev/cl/426274.

This change also specifies GOMODCACHE explicitly when executing Go
commands so that the existing cache is always used. The default
GOMODCACHE is $GOPATH/pkg/mod, and this path varies when a temporary
GOPATH is specified, which results in cold cache.

Before this change (on my MacBook Pro 2020):

$ time go run ./cmd/gomobile/ bind -target ios ./example/bind/hello/

real    0m23.274s
user    0m15.751s
sys     0m10.469s

After this change:

$ time go run ./cmd/gomobile/ bind -target ios ./example/bind/hello/

real    0m8.059s
user    0m13.763s
sys     0m9.004s

Updates golang/go#37902
Updates golang/go#54770

Change-Id: Iaeb077b58c22ab63d28f78972a0af76660883a05
Reviewed-on: https://go-review.googlesource.com/c/mobile/+/442195
Reviewed-by: Changkun Ou <mail@changkun.de>
Run-TryBot: Hajime Hoshi <hajimehoshi@gmail.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
diff --git a/cmd/gomobile/bind_iosapp.go b/cmd/gomobile/bind_iosapp.go
index 47f5e98..a9f7a7f 100644
--- a/cmd/gomobile/bind_iosapp.go
+++ b/cmd/gomobile/bind_iosapp.go
@@ -45,11 +45,11 @@
 	}
 
 	// Run the gobind command for each platform
-	var wg errgroup.Group
+	var gobindWG errgroup.Group
 	for platform, outDir := range outDirsForPlatform {
 		platform := platform
 		outDir := outDir
-		wg.Go(func() error {
+		gobindWG.Go(func() error {
 			// Catalyst support requires iOS 13+
 			v, _ := strconv.ParseFloat(buildIOSVersion, 64)
 			if platform == "maccatalyst" && v < 13.0 {
@@ -78,7 +78,7 @@
 			return nil
 		})
 	}
-	if err := wg.Wait(); err != nil {
+	if err := gobindWG.Wait(); err != nil {
 		return err
 	}
 
@@ -87,12 +87,60 @@
 		return err
 	}
 
+	// Build archive files.
+	var buildWG errgroup.Group
+	for _, t := range targets {
+		t := t
+		buildWG.Go(func() error {
+			outDir := outDirsForPlatform[t.platform]
+			outSrcDir := filepath.Join(outDir, "src")
+
+			if modulesUsed {
+				// Copy the source directory for each architecture for concurrent building.
+				newOutSrcDir := filepath.Join(outDir, "src-"+t.arch)
+				if !buildN {
+					if err := doCopyAll(newOutSrcDir, outSrcDir); err != nil {
+						return err
+					}
+				}
+				outSrcDir = newOutSrcDir
+			}
+
+			// Copy the environment variables to make this function concurrent-safe.
+			env := make([]string, len(appleEnv[t.String()]))
+			copy(env, appleEnv[t.String()])
+
+			// Add the generated packages to GOPATH for reverse bindings.
+			gopath := fmt.Sprintf("GOPATH=%s%c%s", outDir, filepath.ListSeparator, goEnv("GOPATH"))
+			env = append(env, gopath)
+
+			// Run `go mod tidy` to force to create go.sum.
+			// Without go.sum, `go build` fails as of Go 1.16.
+			if modulesUsed {
+				if err := writeGoMod(outSrcDir, t.platform, t.arch); err != nil {
+					return err
+				}
+				if err := goModTidyAt(outSrcDir, env); err != nil {
+					return err
+				}
+			}
+
+			if err := goAppleBindArchive(appleArchiveFilepath(name, t), env, outSrcDir); err != nil {
+				return fmt.Errorf("%s/%s: %v", t.platform, t.arch, err)
+			}
+
+			return nil
+		})
+	}
+	if err := buildWG.Wait(); err != nil {
+		return err
+	}
+
 	var frameworkDirs []string
 	frameworkArchCount := map[string]int{}
 	for _, t := range targets {
 		outDir := outDirsForPlatform[t.platform]
-		outSrcDir := filepath.Join(outDir, "src")
-		gobindDir := filepath.Join(outSrcDir, "gobind")
+		gobindDir := filepath.Join(outDir, "src", "gobind")
 
 		env := appleEnv[t.String()][:]
 		sdk := getenv(env, "DARWIN_SDK")
@@ -101,33 +149,6 @@
 		frameworkDirs = append(frameworkDirs, frameworkDir)
 		frameworkArchCount[frameworkDir] = frameworkArchCount[frameworkDir] + 1
 
-		fileBases := make([]string, len(pkgs)+1)
-		for i, pkg := range pkgs {
-			fileBases[i] = bindPrefix + strings.Title(pkg.Name)
-		}
-		fileBases[len(fileBases)-1] = "Universe"
-
-		// Add the generated packages to GOPATH for reverse bindings.
-		gopath := fmt.Sprintf("GOPATH=%s%c%s", outDir, filepath.ListSeparator, goEnv("GOPATH"))
-		env = append(env, gopath)
-
-		if err := writeGoMod(filepath.Join(outDir, "src"), t.platform, t.arch); err != nil {
-			return err
-		}
-
-		// Run `go mod tidy` to force to create go.sum.
-		// Without go.sum, `go build` fails as of Go 1.16.
-		if modulesUsed {
-			if err := goModTidyAt(outSrcDir, env); err != nil {
-				return err
-			}
-		}
-
-		path, err := goAppleBindArchive(name+"-"+t.platform+"-"+t.arch, env, outSrcDir)
-		if err != nil {
-			return fmt.Errorf("%s/%s: %v", t.platform, t.arch, err)
-		}
-
 		versionsDir := filepath.Join(frameworkDir, "Versions")
 		versionsADir := filepath.Join(versionsDir, "A")
 		titlePath := filepath.Join(versionsADir, title)
@@ -135,7 +156,7 @@
 			// Not the first static lib, attach to a fat library and skip create headers
 			fatCmd := exec.Command(
 				"xcrun",
-				"lipo", path, titlePath, "-create", "-output", titlePath,
+				"lipo", appleArchiveFilepath(name, t), titlePath, "-create", "-output", titlePath,
 			)
 			if err := runCmd(fatCmd); err != nil {
 				return err
@@ -159,12 +180,18 @@
 
 		lipoCmd := exec.Command(
 			"xcrun",
-			"lipo", path, "-create", "-o", titlePath,
+			"lipo", appleArchiveFilepath(name, t), "-create", "-o", titlePath,
 		)
 		if err := runCmd(lipoCmd); err != nil {
 			return err
 		}
 
+		fileBases := make([]string, len(pkgs)+1)
+		for i, pkg := range pkgs {
+			fileBases[i] = bindPrefix + strings.Title(pkg.Name)
+		}
+		fileBases[len(fileBases)-1] = "Universe"
+
 		// Copy header file next to output archive.
 		var headerFiles []string
 		if len(fileBases) == 1 {
@@ -236,7 +263,6 @@
 		if err != nil {
 			return err
 		}
-
 	}
 
 	// Finally combine all frameworks to an XCFramework
@@ -267,13 +293,12 @@
     export *
 }`))
 
-func goAppleBindArchive(name string, env []string, gosrc string) (string, error) {
-	archive := filepath.Join(tmpdir, name+".a")
-	err := goBuildAt(gosrc, "./gobind", env, "-buildmode=c-archive", "-o", archive)
-	if err != nil {
-		return "", err
-	}
-	return archive, nil
+func appleArchiveFilepath(name string, t targetInfo) string {
+	return filepath.Join(tmpdir, name+"-"+t.platform+"-"+t.arch+".a")
+}
+
+func goAppleBindArchive(out string, env []string, gosrc string) error {
+	return goBuildAt(gosrc, "./gobind", env, "-buildmode=c-archive", "-o", out)
 }
 
 var appleBindHeaderTmpl = template.Must(template.New("apple.h").Parse(`
diff --git a/cmd/gomobile/bind_test.go b/cmd/gomobile/bind_test.go
index 318ff62..fa6b0ef 100644
--- a/cmd/gomobile/bind_test.go
+++ b/cmd/gomobile/bind_test.go
@@ -188,8 +188,8 @@
 WORK=$WORK
 GOOS=android CGO_ENABLED=1 gobind -lang=go,java -outdir=$WORK{{if .JavaPkg}} -javapkg={{.JavaPkg}}{{end}} golang.org/x/mobile/asset
 mkdir -p $WORK/src-android-arm
-PWD=$WORK/src-android-arm GOOS=android GOARCH=arm CC=$NDK_PATH/toolchains/llvm/prebuilt/{{.NDKARCH}}/bin/armv7a-linux-androideabi16-clang CXX=$NDK_PATH/toolchains/llvm/prebuilt/{{.NDKARCH}}/bin/armv7a-linux-androideabi16-clang++ CGO_ENABLED=1 GOARM=7 GOPATH=$WORK:$GOPATH go mod tidy
-PWD=$WORK/src-android-arm GOOS=android GOARCH=arm CC=$NDK_PATH/toolchains/llvm/prebuilt/{{.NDKARCH}}/bin/armv7a-linux-androideabi16-clang CXX=$NDK_PATH/toolchains/llvm/prebuilt/{{.NDKARCH}}/bin/armv7a-linux-androideabi16-clang++ CGO_ENABLED=1 GOARM=7 GOPATH=$WORK:$GOPATH go build -x -buildmode=c-shared -o=$WORK/android/src/main/jniLibs/armeabi-v7a/libgojni.so ./gobind
+PWD=$WORK/src-android-arm GOMODCACHE=$GOPATH/pkg/mod GOOS=android GOARCH=arm CC=$NDK_PATH/toolchains/llvm/prebuilt/{{.NDKARCH}}/bin/armv7a-linux-androideabi16-clang CXX=$NDK_PATH/toolchains/llvm/prebuilt/{{.NDKARCH}}/bin/armv7a-linux-androideabi16-clang++ CGO_ENABLED=1 GOARM=7 GOPATH=$WORK:$GOPATH go mod tidy
+PWD=$WORK/src-android-arm GOMODCACHE=$GOPATH/pkg/mod GOOS=android GOARCH=arm CC=$NDK_PATH/toolchains/llvm/prebuilt/{{.NDKARCH}}/bin/armv7a-linux-androideabi16-clang CXX=$NDK_PATH/toolchains/llvm/prebuilt/{{.NDKARCH}}/bin/armv7a-linux-androideabi16-clang++ CGO_ENABLED=1 GOARM=7 GOPATH=$WORK:$GOPATH go build -x -buildmode=c-shared -o=$WORK/android/src/main/jniLibs/armeabi-v7a/libgojni.so ./gobind
 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 .
 `))
@@ -198,9 +198,9 @@
 WORK=$WORK
 rm -r -f "{{.Output}}.xcframework"
 GOOS=ios CGO_ENABLED=1 gobind -lang=go,objc -outdir=$WORK/ios -tags=ios{{if .Prefix}} -prefix={{.Prefix}}{{end}} golang.org/x/mobile/asset
-mkdir -p $WORK/ios/src
-PWD=$WORK/ios/src GOOS=ios GOARCH=arm64 GOFLAGS=-tags=ios CC=iphoneos-clang CXX=iphoneos-clang++ CGO_CFLAGS=-isysroot iphoneos -miphoneos-version-min=13.0 -fembed-bitcode -arch arm64 CGO_CXXFLAGS=-isysroot iphoneos -miphoneos-version-min=13.0 -fembed-bitcode -arch arm64 CGO_LDFLAGS=-isysroot iphoneos -miphoneos-version-min=13.0 -fembed-bitcode -arch arm64 CGO_ENABLED=1 DARWIN_SDK=iphoneos GOPATH=$WORK/ios:$GOPATH go mod tidy
-PWD=$WORK/ios/src GOOS=ios GOARCH=arm64 GOFLAGS=-tags=ios CC=iphoneos-clang CXX=iphoneos-clang++ CGO_CFLAGS=-isysroot iphoneos -miphoneos-version-min=13.0 -fembed-bitcode -arch arm64 CGO_CXXFLAGS=-isysroot iphoneos -miphoneos-version-min=13.0 -fembed-bitcode -arch arm64 CGO_LDFLAGS=-isysroot iphoneos -miphoneos-version-min=13.0 -fembed-bitcode -arch arm64 CGO_ENABLED=1 DARWIN_SDK=iphoneos GOPATH=$WORK/ios:$GOPATH go build -x -buildmode=c-archive -o $WORK/{{.Output}}-ios-arm64.a ./gobind
+mkdir -p $WORK/ios/src-arm64
+PWD=$WORK/ios/src-arm64 GOMODCACHE=$GOPATH/pkg/mod GOOS=ios GOARCH=arm64 GOFLAGS=-tags=ios CC=iphoneos-clang CXX=iphoneos-clang++ CGO_CFLAGS=-isysroot iphoneos -miphoneos-version-min=13.0 -fembed-bitcode -arch arm64 CGO_CXXFLAGS=-isysroot iphoneos -miphoneos-version-min=13.0 -fembed-bitcode -arch arm64 CGO_LDFLAGS=-isysroot iphoneos -miphoneos-version-min=13.0 -fembed-bitcode -arch arm64 CGO_ENABLED=1 DARWIN_SDK=iphoneos GOPATH=$WORK/ios:$GOPATH go mod tidy
+PWD=$WORK/ios/src-arm64 GOMODCACHE=$GOPATH/pkg/mod GOOS=ios GOARCH=arm64 GOFLAGS=-tags=ios CC=iphoneos-clang CXX=iphoneos-clang++ CGO_CFLAGS=-isysroot iphoneos -miphoneos-version-min=13.0 -fembed-bitcode -arch arm64 CGO_CXXFLAGS=-isysroot iphoneos -miphoneos-version-min=13.0 -fembed-bitcode -arch arm64 CGO_LDFLAGS=-isysroot iphoneos -miphoneos-version-min=13.0 -fembed-bitcode -arch arm64 CGO_ENABLED=1 DARWIN_SDK=iphoneos GOPATH=$WORK/ios:$GOPATH go build -x -buildmode=c-archive -o $WORK/{{.Output}}-ios-arm64.a ./gobind
 mkdir -p $WORK/ios/iphoneos/{{.Output}}.framework/Versions/A/Headers
 ln -s A $WORK/ios/iphoneos/{{.Output}}.framework/Versions/Current
 ln -s Versions/Current/Headers $WORK/ios/iphoneos/{{.Output}}.framework/Headers
diff --git a/cmd/gomobile/build.go b/cmd/gomobile/build.go
index 64a28fe..c948343 100644
--- a/cmd/gomobile/build.go
+++ b/cmd/gomobile/build.go
@@ -332,7 +332,15 @@
 	}
 	cmd.Args = append(cmd.Args, args...)
 	cmd.Args = append(cmd.Args, srcs...)
-	cmd.Env = append([]string{}, env...)
+
+	// Specify GOMODCACHE explicitly. The default cache path is GOPATH[0]/pkg/mod,
+	// but the path varies when GOPATH is specified at env, which results in cold cache.
+	if gmc, err := goModCachePath(); err == nil {
+		env = append([]string{"GOMODCACHE=" + gmc}, env...)
+	} else {
+		env = append([]string{}, env...)
+	}
+	cmd.Env = env
 	cmd.Dir = at
 	return runCmd(cmd)
 }
@@ -342,7 +350,15 @@
 	if buildV {
 		cmd.Args = append(cmd.Args, "-v")
 	}
-	cmd.Env = append([]string{}, env...)
+
+	// Specify GOMODCACHE explicitly. The default cache path is GOPATH[0]/pkg/mod,
+	// but the path varies when GOPATH is specified at env, which results in cold cache.
+	if gmc, err := goModCachePath(); err == nil {
+		env = append([]string{"GOMODCACHE=" + gmc}, env...)
+	} else {
+		env = append([]string{}, env...)
+	}
+	cmd.Env = env
 	cmd.Dir = at
 	return runCmd(cmd)
 }
@@ -422,3 +438,11 @@
 func (t targetInfo) String() string {
 	return t.platform + "/" + t.arch
 }
+
+func goModCachePath() (string, error) {
+	out, err := exec.Command("go", "env", "GOMODCACHE").Output()
+	if err != nil {
+		return "", err
+	}
+	return strings.TrimSpace(string(out)), nil
+}
diff --git a/cmd/gomobile/build_darwin_test.go b/cmd/gomobile/build_darwin_test.go
index bb87701..43ca683 100644
--- a/cmd/gomobile/build_darwin_test.go
+++ b/cmd/gomobile/build_darwin_test.go
@@ -107,8 +107,8 @@
 echo "{{template "infoplist" .Xinfo}}" > $WORK/main/Info.plist
 mkdir -p $WORK/main/Images.xcassets/AppIcon.appiconset
 echo "{{.Xcontents}}" > $WORK/main/Images.xcassets/AppIcon.appiconset/Contents.json
-GOOS=ios GOARCH=arm64 GOFLAGS=-tags=ios CC=iphoneos-clang CXX=iphoneos-clang++ CGO_CFLAGS=-isysroot iphoneos -miphoneos-version-min=13.0 -fembed-bitcode -arch arm64 CGO_CXXFLAGS=-isysroot iphoneos -miphoneos-version-min=13.0 -fembed-bitcode -arch arm64 CGO_LDFLAGS=-isysroot iphoneos -miphoneos-version-min=13.0 -fembed-bitcode -arch arm64 CGO_ENABLED=1 DARWIN_SDK=iphoneos go build -tags tag1 -x -ldflags=-w -o=$WORK/ios/arm64 {{.Pkg}}
-GOOS=ios GOARCH=amd64 GOFLAGS=-tags=ios CC=iphonesimulator-clang CXX=iphonesimulator-clang++ CGO_CFLAGS=-isysroot iphonesimulator -mios-simulator-version-min=13.0 -fembed-bitcode -arch x86_64 CGO_CXXFLAGS=-isysroot iphonesimulator -mios-simulator-version-min=13.0 -fembed-bitcode -arch x86_64 CGO_LDFLAGS=-isysroot iphonesimulator -mios-simulator-version-min=13.0 -fembed-bitcode -arch x86_64 CGO_ENABLED=1 DARWIN_SDK=iphonesimulator go build -tags tag1 -x -ldflags=-w -o=$WORK/iossimulator/amd64 {{.Pkg}}
+GOMODCACHE=$GOPATH/pkg/mod GOOS=ios GOARCH=arm64 GOFLAGS=-tags=ios CC=iphoneos-clang CXX=iphoneos-clang++ CGO_CFLAGS=-isysroot iphoneos -miphoneos-version-min=13.0 -fembed-bitcode -arch arm64 CGO_CXXFLAGS=-isysroot iphoneos -miphoneos-version-min=13.0 -fembed-bitcode -arch arm64 CGO_LDFLAGS=-isysroot iphoneos -miphoneos-version-min=13.0 -fembed-bitcode -arch arm64 CGO_ENABLED=1 DARWIN_SDK=iphoneos go build -tags tag1 -x -ldflags=-w -o=$WORK/ios/arm64 {{.Pkg}}
+GOMODCACHE=$GOPATH/pkg/mod GOOS=ios GOARCH=amd64 GOFLAGS=-tags=ios CC=iphonesimulator-clang CXX=iphonesimulator-clang++ CGO_CFLAGS=-isysroot iphonesimulator -mios-simulator-version-min=13.0 -fembed-bitcode -arch x86_64 CGO_CXXFLAGS=-isysroot iphonesimulator -mios-simulator-version-min=13.0 -fembed-bitcode -arch x86_64 CGO_LDFLAGS=-isysroot iphonesimulator -mios-simulator-version-min=13.0 -fembed-bitcode -arch x86_64 CGO_ENABLED=1 DARWIN_SDK=iphonesimulator go build -tags tag1 -x -ldflags=-w -o=$WORK/iossimulator/amd64 {{.Pkg}}
 xcrun lipo -o $WORK/main/main -create $WORK/ios/arm64 $WORK/iossimulator/amd64
 mkdir -p $WORK/main/assets
 xcrun xcodebuild -configuration Release -project $WORK/main.xcodeproj -allowProvisioningUpdates DEVELOPMENT_TEAM={{.TeamID}}
@@ -117,7 +117,7 @@
 
 var appleOtherBuildTmpl = template.Must(infoplistTmpl.New("output").Parse(`GOMOBILE={{.GOPATH}}/pkg/gomobile
 WORK=$WORK
-GOOS=ios GOARCH=arm64 GOFLAGS=-tags=ios CC=iphoneos-clang CXX=iphoneos-clang++ CGO_CFLAGS=-isysroot iphoneos -miphoneos-version-min=13.0 -fembed-bitcode -arch arm64 CGO_CXXFLAGS=-isysroot iphoneos -miphoneos-version-min=13.0 -fembed-bitcode -arch arm64 CGO_LDFLAGS=-isysroot iphoneos -miphoneos-version-min=13.0 -fembed-bitcode -arch arm64 CGO_ENABLED=1 DARWIN_SDK=iphoneos go build -tags tag1 -x {{.Pkg}}
-GOOS=ios GOARCH=arm64 GOFLAGS=-tags=ios CC=iphonesimulator-clang CXX=iphonesimulator-clang++ CGO_CFLAGS=-isysroot iphonesimulator -mios-simulator-version-min=13.0 -fembed-bitcode -arch arm64 CGO_CXXFLAGS=-isysroot iphonesimulator -mios-simulator-version-min=13.0 -fembed-bitcode -arch arm64 CGO_LDFLAGS=-isysroot iphonesimulator -mios-simulator-version-min=13.0 -fembed-bitcode -arch arm64 CGO_ENABLED=1 DARWIN_SDK=iphonesimulator go build -tags tag1 -x {{.Pkg}}
-GOOS=ios GOARCH=amd64 GOFLAGS=-tags=ios CC=iphonesimulator-clang CXX=iphonesimulator-clang++ CGO_CFLAGS=-isysroot iphonesimulator -mios-simulator-version-min=13.0 -fembed-bitcode -arch x86_64 CGO_CXXFLAGS=-isysroot iphonesimulator -mios-simulator-version-min=13.0 -fembed-bitcode -arch x86_64 CGO_LDFLAGS=-isysroot iphonesimulator -mios-simulator-version-min=13.0 -fembed-bitcode -arch x86_64 CGO_ENABLED=1 DARWIN_SDK=iphonesimulator go build -tags tag1 -x {{.Pkg}}
+GOMODCACHE=$GOPATH/pkg/mod GOOS=ios GOARCH=arm64 GOFLAGS=-tags=ios CC=iphoneos-clang CXX=iphoneos-clang++ CGO_CFLAGS=-isysroot iphoneos -miphoneos-version-min=13.0 -fembed-bitcode -arch arm64 CGO_CXXFLAGS=-isysroot iphoneos -miphoneos-version-min=13.0 -fembed-bitcode -arch arm64 CGO_LDFLAGS=-isysroot iphoneos -miphoneos-version-min=13.0 -fembed-bitcode -arch arm64 CGO_ENABLED=1 DARWIN_SDK=iphoneos go build -tags tag1 -x {{.Pkg}}
+GOMODCACHE=$GOPATH/pkg/mod GOOS=ios GOARCH=arm64 GOFLAGS=-tags=ios CC=iphonesimulator-clang CXX=iphonesimulator-clang++ CGO_CFLAGS=-isysroot iphonesimulator -mios-simulator-version-min=13.0 -fembed-bitcode -arch arm64 CGO_CXXFLAGS=-isysroot iphonesimulator -mios-simulator-version-min=13.0 -fembed-bitcode -arch arm64 CGO_LDFLAGS=-isysroot iphonesimulator -mios-simulator-version-min=13.0 -fembed-bitcode -arch arm64 CGO_ENABLED=1 DARWIN_SDK=iphonesimulator go build -tags tag1 -x {{.Pkg}}
+GOMODCACHE=$GOPATH/pkg/mod GOOS=ios GOARCH=amd64 GOFLAGS=-tags=ios CC=iphonesimulator-clang CXX=iphonesimulator-clang++ CGO_CFLAGS=-isysroot iphonesimulator -mios-simulator-version-min=13.0 -fembed-bitcode -arch x86_64 CGO_CXXFLAGS=-isysroot iphonesimulator -mios-simulator-version-min=13.0 -fembed-bitcode -arch x86_64 CGO_LDFLAGS=-isysroot iphonesimulator -mios-simulator-version-min=13.0 -fembed-bitcode -arch x86_64 CGO_ENABLED=1 DARWIN_SDK=iphonesimulator go build -tags tag1 -x {{.Pkg}}
 `))
diff --git a/cmd/gomobile/build_test.go b/cmd/gomobile/build_test.go
index 448c6dd..b50aba7 100644
--- a/cmd/gomobile/build_test.go
+++ b/cmd/gomobile/build_test.go
@@ -113,7 +113,7 @@
 var androidBuildTmpl = template.Must(template.New("output").Parse(`GOMOBILE={{.GOPATH}}/pkg/gomobile
 WORK=$WORK
 mkdir -p $WORK/lib/armeabi-v7a
-GOOS=android GOARCH=arm CC=$NDK_PATH/toolchains/llvm/prebuilt/{{.NDKARCH}}/bin/armv7a-linux-androideabi16-clang CXX=$NDK_PATH/toolchains/llvm/prebuilt/{{.NDKARCH}}/bin/armv7a-linux-androideabi16-clang++ CGO_ENABLED=1 GOARM=7 go build -tags tag1 -x -buildmode=c-shared -o $WORK/lib/armeabi-v7a/libbasic.so golang.org/x/mobile/example/basic
+GOMODCACHE=$GOPATH/pkg/mod GOOS=android GOARCH=arm CC=$NDK_PATH/toolchains/llvm/prebuilt/{{.NDKARCH}}/bin/armv7a-linux-androideabi16-clang CXX=$NDK_PATH/toolchains/llvm/prebuilt/{{.NDKARCH}}/bin/armv7a-linux-androideabi16-clang++ CGO_ENABLED=1 GOARM=7 go build -tags tag1 -x -buildmode=c-shared -o $WORK/lib/armeabi-v7a/libbasic.so golang.org/x/mobile/example/basic
 `))
 
 func TestParseBuildTarget(t *testing.T) {
diff --git a/cmd/gomobile/init_test.go b/cmd/gomobile/init_test.go
index 5a1482b..2eabda1 100644
--- a/cmd/gomobile/init_test.go
+++ b/cmd/gomobile/init_test.go
@@ -175,7 +175,7 @@
 rm -r -f "$GOMOBILE"
 mkdir -p $GOMOBILE
 WORK={{.GOPATH}}/pkg/gomobile/work
-go install -x golang.org/x/mobile/cmd/gobind@latest
+GOMODCACHE={{.GOPATH}}/pkg/mod go install -x golang.org/x/mobile/cmd/gobind@latest
 cp $OPENAL_PATH/include/AL/al.h $GOMOBILE/include/AL/al.h
 mkdir -p $GOMOBILE/include/AL
 cp $OPENAL_PATH/include/AL/alc.h $GOMOBILE/include/AL/alc.h