The seq serialization machinery is a historic artifact from when Go mobile code had to run in a separate process. Now that Go code is running in-process, replace the explicit serialization with direct calls and pass arguments on the stack. The benefits are a much smaller bind runtime, much less garbage (and, in Java, fewer objects with finalizers), less argument copying, and faster cross-language calls. The cost is a more complex generator, because some of the work from the bind runtime is moved to generated code. Generated code now handles conversion between Go and Java/ObjC types, multiple return values and memory management of byte slice and string arguments. To overcome the lack of calling C code between Go packages, all bound packages now end up in the same (fake) package, "gomobile_bind", instead of separate packages (go_<pkgname>). To avoid name clashes, the package name is added as a prefix to generated functions and types. Also, don't copy byte arrays passed to Go, saving call time and allowing read([]byte)-style interfaces to foreign callers (#12113). Finally, add support for nil interfaces and struct pointers to objc. This is a large CL, but most of the changes stem from changing testdata. The full benchcmp output on the CL/20095 benchmarks on my Nexus 5 is reproduced below. Note that the savings for the JavaSlice* benchmarks are skewed because byte slices are no longer copied before passing them to Go. benchmark old ns/op new ns/op delta BenchmarkJavaEmpty 26.0 19.0 -26.92% BenchmarkJavaEmptyDirect 23.0 22.0 -4.35% BenchmarkJavaNoargs 7685 2339 -69.56% BenchmarkJavaNoargsDirect 17405 8041 -53.80% BenchmarkJavaOnearg 26887 2366 -91.20% BenchmarkJavaOneargDirect 34266 7910 -76.92% BenchmarkJavaOneret 38325 2245 -94.14% BenchmarkJavaOneretDirect 46265 7708 -83.34% BenchmarkJavaManyargs 41720 2535 -93.92% BenchmarkJavaManyargsDirect 51026 8373 -83.59% BenchmarkJavaRefjava 38139 21260 -44.26% BenchmarkJavaRefjavaDirect 42706 28150 -34.08% BenchmarkJavaRefgo 34403 6843 -80.11% BenchmarkJavaRefgoDirect 40193 16582 -58.74% BenchmarkJavaStringShort 32366 9323 -71.20% BenchmarkJavaStringShortDirect 41973 19118 -54.45% BenchmarkJavaStringLong 127879 94420 -26.16% BenchmarkJavaStringLongDirect 133776 114760 -14.21% BenchmarkJavaStringShortUnicode 32562 9221 -71.68% BenchmarkJavaStringShortUnicodeDirect 41464 19094 -53.95% BenchmarkJavaStringLongUnicode 131015 89401 -31.76% BenchmarkJavaStringLongUnicodeDirect 134130 90786 -32.31% BenchmarkJavaSliceShort 42462 7538 -82.25% BenchmarkJavaSliceShortDirect 52940 17017 -67.86% BenchmarkJavaSliceLong 138391 8466 -93.88% BenchmarkJavaSliceLongDirect 205804 15666 -92.39% BenchmarkGoEmpty 3.00 3.00 +0.00% BenchmarkGoEmptyDirect 3.00 3.00 +0.00% BenchmarkGoNoarg 40342 13716 -66.00% BenchmarkGoNoargDirect 46691 13569 -70.94% BenchmarkGoOnearg 43529 13757 -68.40% BenchmarkGoOneargDirect 44867 14078 -68.62% BenchmarkGoOneret 45456 13559 -70.17% BenchmarkGoOneretDirect 44694 13442 -69.92% BenchmarkGoRefjava 55111 28071 -49.06% BenchmarkGoRefjavaDirect 60883 26872 -55.86% BenchmarkGoRefgo 57038 29223 -48.77% BenchmarkGoRefgoDirect 56153 27812 -50.47% BenchmarkGoManyargs 67967 17398 -74.40% BenchmarkGoManyargsDirect 60617 16998 -71.96% BenchmarkGoStringShort 57538 22600 -60.72% BenchmarkGoStringShortDirect 52627 22704 -56.86% BenchmarkGoStringLong 128485 52530 -59.12% BenchmarkGoStringLongDirect 138377 52079 -62.36% BenchmarkGoStringShortUnicode 57062 22994 -59.70% BenchmarkGoStringShortUnicodeDirect 62563 22938 -63.34% BenchmarkGoStringLongUnicode 139913 55553 -60.29% BenchmarkGoStringLongUnicodeDirect 150863 57791 -61.69% BenchmarkGoSliceShort 59279 20215 -65.90% BenchmarkGoSliceShortDirect 60160 21136 -64.87% BenchmarkGoSliceLong 411225 301870 -26.59% BenchmarkGoSliceLongDirect 399029 298915 -25.09% Fixes golang/go#12619 Fixes golang/go#12113 Fixes golang/go#13033 Change-Id: I2b45e9e98a1248e3c23a5137f775f7364908bec7 Reviewed-on: https://go-review.googlesource.com/19821 Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
429 lines
9.8 KiB
Go
429 lines
9.8 KiB
Go
// Copyright 2015 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package main
|
|
|
|
import (
|
|
"archive/zip"
|
|
"fmt"
|
|
"go/build"
|
|
"io"
|
|
"io/ioutil"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
func goAndroidBind(pkgs []*build.Package, androidArchs []string) error {
|
|
if sdkDir := os.Getenv("ANDROID_HOME"); sdkDir == "" {
|
|
return fmt.Errorf("this command requires ANDROID_HOME environment variable (path to the Android SDK)")
|
|
}
|
|
var androidArgs []string
|
|
if goVersion == go1_6 {
|
|
// Ideally this would be -buildmode=c-shared.
|
|
// https://golang.org/issue/13234.
|
|
androidArgs = []string{"-gcflags=-shared", "-ldflags=-shared"}
|
|
}
|
|
|
|
paths := make([]string, len(pkgs))
|
|
for i, p := range pkgs {
|
|
paths[i] = p.ImportPath
|
|
}
|
|
|
|
androidDir := filepath.Join(tmpdir, "android")
|
|
mainFile := filepath.Join(tmpdir, "androidlib/main.go")
|
|
|
|
// Generate binding code and java source code only when processing the first package.
|
|
first := true
|
|
for _, arch := range androidArchs {
|
|
env := androidEnv[arch]
|
|
toolchain := ndk.Toolchain(arch)
|
|
|
|
if !first {
|
|
if err := goInstall(paths, env, androidArgs...); err != nil {
|
|
return err
|
|
}
|
|
err := goBuild(
|
|
mainFile,
|
|
env,
|
|
"-buildmode=c-shared",
|
|
"-o="+filepath.Join(androidDir, "src/main/jniLibs/"+toolchain.abi+"/libgojni.so"),
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
continue
|
|
}
|
|
first = false
|
|
|
|
typesPkgs, err := loadExportData(pkgs, env, androidArgs...)
|
|
if err != nil {
|
|
return fmt.Errorf("loadExportData failed %v", err)
|
|
}
|
|
|
|
binder, err := newBinder(typesPkgs)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
srcDir := filepath.Join(tmpdir, "gomobile_bind")
|
|
for _, pkg := range typesPkgs {
|
|
if err := binder.GenGo(pkg, srcDir); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
err = writeFile(mainFile, func(w io.Writer) error {
|
|
_, err := w.Write(androidMainFile)
|
|
return err
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create the main package for android: %v", err)
|
|
}
|
|
|
|
p, err := ctx.Import("golang.org/x/mobile/bind", cwd, build.ImportComment)
|
|
if err != nil {
|
|
return fmt.Errorf(`"golang.org/x/mobile/bind" is not found; run go get golang.org/x/mobile/bind`)
|
|
}
|
|
repo := filepath.Clean(filepath.Join(p.Dir, "..")) // golang.org/x/mobile directory.
|
|
|
|
for _, pkg := range binder.pkgs {
|
|
pkgpath := strings.Replace(bindJavaPkg, ".", "/", -1)
|
|
if bindJavaPkg == "" {
|
|
pkgpath = "go/" + pkg.Name()
|
|
}
|
|
if err := binder.GenJava(pkg, srcDir, filepath.Join(androidDir, "src/main/java/"+pkgpath)); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if err := binder.GenJavaSupport(srcDir); err != nil {
|
|
return err
|
|
}
|
|
if err := binder.GenGoSupport(srcDir); err != nil {
|
|
return err
|
|
}
|
|
|
|
javaDir := filepath.Join(androidDir, "src/main/java/go")
|
|
if err := mkdir(javaDir); err != nil {
|
|
return err
|
|
}
|
|
err = goBuild(
|
|
mainFile,
|
|
env,
|
|
"-buildmode=c-shared",
|
|
"-o="+filepath.Join(androidDir, "src/main/jniLibs/"+toolchain.abi+"/libgojni.so"),
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, javaFile := range []string{"Seq.java", "LoadJNI.java"} {
|
|
src := filepath.Join(repo, "bind/java/"+javaFile)
|
|
dst := filepath.Join(javaDir, javaFile)
|
|
rm(dst)
|
|
if err := symlink(src, dst); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
return buildAAR(androidDir, pkgs, androidArchs)
|
|
}
|
|
|
|
var androidMainFile = []byte(`
|
|
package main
|
|
|
|
import (
|
|
_ "golang.org/x/mobile/bind/java"
|
|
_ "../gomobile_bind"
|
|
)
|
|
|
|
func main() {}
|
|
`)
|
|
|
|
// AAR is the format for the binary distribution of an Android Library Project
|
|
// and it is a ZIP archive with extension .aar.
|
|
// http://tools.android.com/tech-docs/new-build-system/aar-format
|
|
//
|
|
// These entries are directly at the root of the archive.
|
|
//
|
|
// AndroidManifest.xml (mandatory)
|
|
// classes.jar (mandatory)
|
|
// assets/ (optional)
|
|
// jni/<abi>/libgojni.so
|
|
// R.txt (mandatory)
|
|
// res/ (mandatory)
|
|
// libs/*.jar (optional, not relevant)
|
|
// proguard.txt (optional)
|
|
// lint.jar (optional, not relevant)
|
|
// aidl (optional, not relevant)
|
|
//
|
|
// javac and jar commands are needed to build classes.jar.
|
|
func buildAAR(androidDir string, pkgs []*build.Package, androidArchs []string) (err error) {
|
|
var out io.Writer = ioutil.Discard
|
|
if buildO == "" {
|
|
buildO = pkgs[0].Name + ".aar"
|
|
}
|
|
if !strings.HasSuffix(buildO, ".aar") {
|
|
return fmt.Errorf("output file name %q does not end in '.aar'", buildO)
|
|
}
|
|
if !buildN {
|
|
f, err := os.Create(buildO)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer func() {
|
|
if cerr := f.Close(); err == nil {
|
|
err = cerr
|
|
}
|
|
}()
|
|
out = f
|
|
}
|
|
|
|
aarw := zip.NewWriter(out)
|
|
aarwcreate := func(name string) (io.Writer, error) {
|
|
if buildV {
|
|
fmt.Fprintf(os.Stderr, "aar: %s\n", name)
|
|
}
|
|
return aarw.Create(name)
|
|
}
|
|
w, err := aarwcreate("AndroidManifest.xml")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
const manifestFmt = `<manifest xmlns:android="http://schemas.android.com/apk/res/android" package=%q>
|
|
<uses-sdk android:minSdkVersion="%d"/></manifest>`
|
|
fmt.Fprintf(w, manifestFmt, "go."+pkgs[0].Name+".gojni", minAndroidAPI)
|
|
|
|
w, err = aarwcreate("proguard.txt")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fmt.Fprintln(w, `-keep class go.** { *; }`)
|
|
|
|
w, err = aarwcreate("classes.jar")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
src := filepath.Join(androidDir, "src/main/java")
|
|
if err := buildJar(w, src); err != nil {
|
|
return err
|
|
}
|
|
|
|
files := map[string]string{}
|
|
for _, pkg := range pkgs {
|
|
assetsDir := filepath.Join(pkg.Dir, "assets")
|
|
assetsDirExists := false
|
|
if fi, err := os.Stat(assetsDir); err == nil {
|
|
assetsDirExists = fi.IsDir()
|
|
} else if !os.IsNotExist(err) {
|
|
return err
|
|
}
|
|
|
|
if assetsDirExists {
|
|
err := filepath.Walk(
|
|
assetsDir, func(path string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if info.IsDir() {
|
|
return nil
|
|
}
|
|
f, err := os.Open(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer f.Close()
|
|
name := "assets/" + path[len(assetsDir)+1:]
|
|
if orig, exists := files[name]; exists {
|
|
return fmt.Errorf("package %s asset name conflict: %s already added from package %s",
|
|
pkg.ImportPath, name, orig)
|
|
}
|
|
files[name] = pkg.ImportPath
|
|
w, err := aarwcreate(name)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
_, err = io.Copy(w, f)
|
|
return err
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
for _, arch := range androidArchs {
|
|
toolchain := ndk.Toolchain(arch)
|
|
lib := toolchain.abi + "/libgojni.so"
|
|
w, err = aarwcreate("jni/" + lib)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !buildN {
|
|
r, err := os.Open(filepath.Join(androidDir, "src/main/jniLibs/"+lib))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer r.Close()
|
|
if _, err := io.Copy(w, r); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
// TODO(hyangah): do we need to use aapt to create R.txt?
|
|
w, err = aarwcreate("R.txt")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
w, err = aarwcreate("res/")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return aarw.Close()
|
|
}
|
|
|
|
const (
|
|
javacTargetVer = "1.7"
|
|
minAndroidAPI = 15
|
|
)
|
|
|
|
func buildJar(w io.Writer, srcDir string) error {
|
|
var srcFiles []string
|
|
if buildN {
|
|
srcFiles = []string{"*.java"}
|
|
} else {
|
|
err := filepath.Walk(srcDir, func(path string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if filepath.Ext(path) == ".java" {
|
|
srcFiles = append(srcFiles, filepath.Join(".", path[len(srcDir):]))
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
dst := filepath.Join(tmpdir, "javac-output")
|
|
if !buildN {
|
|
if err := os.MkdirAll(dst, 0700); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
apiPath, err := androidAPIPath()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
args := []string{
|
|
"-d", dst,
|
|
"-source", javacTargetVer,
|
|
"-target", javacTargetVer,
|
|
"-bootclasspath", filepath.Join(apiPath, "android.jar"),
|
|
}
|
|
args = append(args, srcFiles...)
|
|
|
|
javac := exec.Command("javac", args...)
|
|
javac.Dir = srcDir
|
|
if err := runCmd(javac); err != nil {
|
|
return err
|
|
}
|
|
|
|
if buildX {
|
|
printcmd("jar c -C %s .", dst)
|
|
}
|
|
if buildN {
|
|
return nil
|
|
}
|
|
jarw := zip.NewWriter(w)
|
|
jarwcreate := func(name string) (io.Writer, error) {
|
|
if buildV {
|
|
fmt.Fprintf(os.Stderr, "jar: %s\n", name)
|
|
}
|
|
return jarw.Create(name)
|
|
}
|
|
f, err := jarwcreate("META-INF/MANIFEST.MF")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fmt.Fprintf(f, manifestHeader)
|
|
|
|
err = filepath.Walk(dst, func(path string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if info.IsDir() {
|
|
return nil
|
|
}
|
|
out, err := jarwcreate(filepath.ToSlash(path[len(dst)+1:]))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
in, err := os.Open(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer in.Close()
|
|
_, err = io.Copy(out, in)
|
|
return err
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return jarw.Close()
|
|
}
|
|
|
|
// androidAPIPath returns an android SDK platform directory under ANDROID_HOME.
|
|
// If there are multiple platforms that satisfy the minimum version requirement
|
|
// androidAPIPath returns the latest one among them.
|
|
func androidAPIPath() (string, error) {
|
|
sdk := os.Getenv("ANDROID_HOME")
|
|
if sdk == "" {
|
|
return "", fmt.Errorf("ANDROID_HOME environment var is not set")
|
|
}
|
|
sdkDir, err := os.Open(filepath.Join(sdk, "platforms"))
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to find android SDK platform: %v", err)
|
|
}
|
|
defer sdkDir.Close()
|
|
fis, err := sdkDir.Readdir(-1)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to find android SDK platform (min API level: %d): %v", minAndroidAPI, err)
|
|
}
|
|
|
|
var apiPath string
|
|
var apiVer int
|
|
for _, fi := range fis {
|
|
name := fi.Name()
|
|
if !fi.IsDir() || !strings.HasPrefix(name, "android-") {
|
|
continue
|
|
}
|
|
n, err := strconv.Atoi(name[len("android-"):])
|
|
if err != nil || n < minAndroidAPI {
|
|
continue
|
|
}
|
|
p := filepath.Join(sdkDir.Name(), name)
|
|
_, err = os.Stat(filepath.Join(p, "android.jar"))
|
|
if err == nil && apiVer < n {
|
|
apiPath = p
|
|
apiVer = n
|
|
}
|
|
}
|
|
if apiVer == 0 {
|
|
return "", fmt.Errorf("failed to find android SDK platform (min API level: %d) in %s",
|
|
minAndroidAPI, sdkDir.Name())
|
|
}
|
|
return apiPath, nil
|
|
}
|