cmd/gomobile: concurrent gomobile-bind building for Android
This speeds up gomobile-bind for Android by concurrent building for
each architecture.
Before this change (on my MacBook Pro 2020):
```
$ time go run ./cmd/gomobile/ bind -target android ./example/bind/hello/
real 0m22.555s
user 0m14.859s
sys 0m10.232s
```
After this change:
```
$ time go run ./cmd/gomobile/ bind -target android ./example/bind/hello/
real 0m9.404s
user 0m15.846s
sys 0m11.044s
```
For #54770
Change-Id: I5a709dd4422a569e9244e924bd43ad2da1ede164
Reviewed-on: https://go-review.googlesource.com/c/mobile/+/426274
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Bryan Mills <bcmills@google.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Reviewed-by: Changkun Ou <mail@changkun.de>
Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org>
Run-TryBot: Hajime Hoshi <hajimehoshi@gmail.com>
diff --git a/cmd/gomobile/bind.go b/cmd/gomobile/bind.go
index c4552e3..54eaa4b 100644
--- a/cmd/gomobile/bind.go
+++ b/cmd/gomobile/bind.go
@@ -15,6 +15,7 @@
"os/exec"
"path/filepath"
"strings"
+ "sync"
"golang.org/x/mobile/internal/sdkpath"
"golang.org/x/mod/modfile"
@@ -282,7 +283,7 @@
return f, nil
}
-// writeGoMod writes go.mod file at $WORK/src when Go modules are used.
+// writeGoMod writes go.mod file at dir when Go modules are used.
func writeGoMod(dir, targetPlatform, targetArch string) error {
m, err := areGoModulesUsed()
if err != nil {
@@ -293,7 +294,7 @@
return nil
}
- return writeFile(filepath.Join(dir, "src", "go.mod"), func(w io.Writer) error {
+ return writeFile(filepath.Join(dir, "go.mod"), func(w io.Writer) error {
f, err := getModuleVersions(targetPlatform, targetArch, ".")
if err != nil {
return err
@@ -312,14 +313,23 @@
})
}
+var (
+ areGoModulesUsedResult struct {
+ used bool
+ err error
+ }
+ areGoModulesUsedOnce sync.Once
+)
+
func areGoModulesUsed() (bool, error) {
- out, err := exec.Command("go", "env", "GOMOD").Output()
- if err != nil {
- return false, err
- }
- outstr := strings.TrimSpace(string(out))
- if outstr == "" {
- return false, nil
- }
- return true, nil
+ areGoModulesUsedOnce.Do(func() {
+ out, err := exec.Command("go", "env", "GOMOD").Output()
+ if err != nil {
+ areGoModulesUsedResult.err = err
+ return
+ }
+ outstr := strings.TrimSpace(string(out))
+ areGoModulesUsedResult.used = outstr != ""
+ })
+ return areGoModulesUsedResult.used, areGoModulesUsedResult.err
}
diff --git a/cmd/gomobile/bind_androidapp.go b/cmd/gomobile/bind_androidapp.go
index a56fd82..3fa9cfa 100644
--- a/cmd/gomobile/bind_androidapp.go
+++ b/cmd/gomobile/bind_androidapp.go
@@ -15,6 +15,7 @@
"strings"
"golang.org/x/mobile/internal/sdkpath"
+ "golang.org/x/sync/errgroup"
"golang.org/x/tools/go/packages"
)
@@ -52,41 +53,16 @@
androidDir := filepath.Join(tmpdir, "android")
- modulesUsed, err := areGoModulesUsed()
- if err != nil {
- return err
- }
-
// Generate binding code and java source code only when processing the first package.
+ var wg errgroup.Group
for _, t := range targets {
- if err := writeGoMod(tmpdir, "android", t.arch); err != nil {
- return err
- }
-
- env := androidEnv[t.arch]
- // Add the generated packages to GOPATH for reverse bindings.
- gopath := fmt.Sprintf("GOPATH=%s%c%s", tmpdir, 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 := goModTidyAt(filepath.Join(tmpdir, "src"), env); err != nil {
- return err
- }
- }
-
- toolchain := ndk.Toolchain(t.arch)
- err := goBuildAt(
- filepath.Join(tmpdir, "src"),
- "./gobind",
- env,
- "-buildmode=c-shared",
- "-o="+filepath.Join(androidDir, "src/main/jniLibs/"+toolchain.abi+"/libgojni.so"),
- )
- if err != nil {
- return err
- }
+ t := t
+ wg.Go(func() error {
+ return buildAndroidSO(androidDir, t.arch)
+ })
+ }
+ if err := wg.Wait(); err != nil {
+ return err
}
jsrc := filepath.Join(tmpdir, "java")
@@ -370,3 +346,56 @@
}
return jarw.Close()
}
+
+// buildAndroidSO generates an Android libgojni.so file to outputDir.
+// buildAndroidSO is concurrent-safe.
+func buildAndroidSO(outputDir string, arch string) error {
+ // Copy the environment variables to make this function concurrent-safe.
+ env := make([]string, len(androidEnv[arch]))
+ copy(env, androidEnv[arch])
+
+ // Add the generated packages to GOPATH for reverse bindings.
+ gopath := fmt.Sprintf("GOPATH=%s%c%s", tmpdir, filepath.ListSeparator, goEnv("GOPATH"))
+ env = append(env, gopath)
+
+ modulesUsed, err := areGoModulesUsed()
+ if err != nil {
+ return err
+ }
+
+ srcDir := filepath.Join(tmpdir, "src")
+
+ if modulesUsed {
+ // Copy the source directory for each architecture for concurrent building.
+ newSrcDir := filepath.Join(tmpdir, "src-android-"+arch)
+ if !buildN {
+ if err := doCopyAll(newSrcDir, srcDir); err != nil {
+ return err
+ }
+ }
+ srcDir = newSrcDir
+
+ if err := writeGoMod(srcDir, "android", 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 err := goModTidyAt(srcDir, env); err != nil {
+ return err
+ }
+ }
+
+ toolchain := ndk.Toolchain(arch)
+ if err := goBuildAt(
+ srcDir,
+ "./gobind",
+ env,
+ "-buildmode=c-shared",
+ "-o="+filepath.Join(outputDir, "src", "main", "jniLibs", toolchain.abi, "libgojni.so"),
+ ); err != nil {
+ return err
+ }
+
+ return nil
+}
diff --git a/cmd/gomobile/bind_iosapp.go b/cmd/gomobile/bind_iosapp.go
index bf0f37d..b9beaea 100644
--- a/cmd/gomobile/bind_iosapp.go
+++ b/cmd/gomobile/bind_iosapp.go
@@ -93,7 +93,7 @@
gopath := fmt.Sprintf("GOPATH=%s%c%s", outDir, filepath.ListSeparator, goEnv("GOPATH"))
env = append(env, gopath)
- if err := writeGoMod(outDir, t.platform, t.arch); err != nil {
+ if err := writeGoMod(filepath.Join(outDir, "src"), t.platform, t.arch); err != nil {
return err
}
diff --git a/cmd/gomobile/bind_test.go b/cmd/gomobile/bind_test.go
index cac3511..318ff62 100644
--- a/cmd/gomobile/bind_test.go
+++ b/cmd/gomobile/bind_test.go
@@ -187,9 +187,9 @@
var bindAndroidTmpl = template.Must(template.New("output").Parse(`GOMOBILE={{.GOPATH}}/pkg/gomobile
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
-PWD=$WORK/src 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 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
+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/java javac -d $WORK/javac-output -source 1.7 -target 1.7 -bootclasspath {{.AndroidPlatform}}/android.jar *.java
jar c -C $WORK/javac-output .
`))
diff --git a/go.mod b/go.mod
index 765a365..fd0ee23 100644
--- a/go.mod
+++ b/go.mod
@@ -6,5 +6,6 @@
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56
golang.org/x/image v0.0.0-20190802002840-cff245a6509b
golang.org/x/mod v0.4.2
+ golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde
golang.org/x/tools v0.1.8-0.20211022200916-316ba0b74098
)
diff --git a/go.sum b/go.sum
index d4d3ebf..b20722d 100644
--- a/go.sum
+++ b/go.sum
@@ -18,6 +18,8 @@
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde h1:ejfdSekXMDxDLbRrJMwUk6KnSLZ2McaUCVcIKM+N6jc=
+golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=