Skip to content

Commit

Permalink
cmd/gomobile: concurrent gomobile-bind building for Android
Browse files Browse the repository at this point in the history
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 <[email protected]>
Reviewed-by: Bryan Mills <[email protected]>
Reviewed-by: Dmitri Shuralyov <[email protected]>
Reviewed-by: Changkun Ou <[email protected]>
Reviewed-by: Dmitri Shuralyov <[email protected]>
Run-TryBot: Hajime Hoshi <[email protected]>
  • Loading branch information
hajimehoshi committed Sep 28, 2022
1 parent aaac322 commit fa6bcb0
Show file tree
Hide file tree
Showing 6 changed files with 90 additions and 48 deletions.
32 changes: 21 additions & 11 deletions cmd/gomobile/bind.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"os/exec"
"path/filepath"
"strings"
"sync"

"golang.org/x/mobile/internal/sdkpath"
"golang.org/x/mod/modfile"
Expand Down Expand Up @@ -282,7 +283,7 @@ func getModuleVersions(targetPlatform string, targetArch string, src string) (*m
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 {
Expand All @@ -293,7 +294,7 @@ func writeGoMod(dir, targetPlatform, targetArch string) error {
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
Expand All @@ -312,14 +313,23 @@ func writeGoMod(dir, targetPlatform, targetArch string) error {
})
}

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
var (
areGoModulesUsedResult struct {
used bool
err error
}
return true, nil
areGoModulesUsedOnce sync.Once
)

func areGoModulesUsed() (bool, error) {
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
}
95 changes: 62 additions & 33 deletions cmd/gomobile/bind_androidapp.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"strings"

"golang.org/x/mobile/internal/sdkpath"
"golang.org/x/sync/errgroup"
"golang.org/x/tools/go/packages"
)

Expand Down Expand Up @@ -52,41 +53,16 @@ func goAndroidBind(gobind string, pkgs []*packages.Package, targets []targetInfo

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")
Expand Down Expand Up @@ -370,3 +346,56 @@ func writeJar(w io.Writer, dir string) error {
}
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
}
2 changes: 1 addition & 1 deletion cmd/gomobile/bind_iosapp.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ func goAppleBind(gobind string, pkgs []*packages.Package, targets []targetInfo)
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
}

Expand Down
6 changes: 3 additions & 3 deletions cmd/gomobile/bind_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,9 +187,9 @@ func TestBindApple(t *testing.T) {
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 .
`))
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ require (
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
)
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
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=
Expand Down

0 comments on commit fa6bcb0

Please sign in to comment.