2015-07-10 16:47:46 -06:00
|
|
|
// 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"
|
|
|
|
"io"
|
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
|
|
|
"os/exec"
|
|
|
|
"path/filepath"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
2019-11-12 21:44:24 +09:00
|
|
|
|
|
|
|
"golang.org/x/tools/go/packages"
|
2015-07-10 16:47:46 -06:00
|
|
|
)
|
|
|
|
|
2019-11-12 21:44:24 +09:00
|
|
|
func goAndroidBind(gobind string, pkgs []*packages.Package, androidArchs []string) error {
|
2015-07-10 16:47:46 -06:00
|
|
|
if sdkDir := os.Getenv("ANDROID_HOME"); sdkDir == "" {
|
|
|
|
return fmt.Errorf("this command requires ANDROID_HOME environment variable (path to the Android SDK)")
|
|
|
|
}
|
|
|
|
|
2018-03-07 22:51:37 +01:00
|
|
|
// Run gobind to generate the bindings
|
|
|
|
cmd := exec.Command(
|
2018-03-16 08:34:03 +01:00
|
|
|
gobind,
|
2018-03-07 22:51:37 +01:00
|
|
|
"-lang=go,java",
|
|
|
|
"-outdir="+tmpdir,
|
|
|
|
)
|
2018-03-09 10:32:43 +01:00
|
|
|
cmd.Env = append(cmd.Env, "GOOS=android")
|
2018-04-19 23:38:10 +02:00
|
|
|
cmd.Env = append(cmd.Env, "CGO_ENABLED=1")
|
2018-03-07 22:51:37 +01:00
|
|
|
if len(ctx.BuildTags) > 0 {
|
|
|
|
cmd.Args = append(cmd.Args, "-tags="+strings.Join(ctx.BuildTags, ","))
|
|
|
|
}
|
|
|
|
if bindJavaPkg != "" {
|
|
|
|
cmd.Args = append(cmd.Args, "-javapkg="+bindJavaPkg)
|
|
|
|
}
|
|
|
|
if bindClasspath != "" {
|
|
|
|
cmd.Args = append(cmd.Args, "-classpath="+bindClasspath)
|
|
|
|
}
|
|
|
|
if bindBootClasspath != "" {
|
|
|
|
cmd.Args = append(cmd.Args, "-bootclasspath="+bindBootClasspath)
|
|
|
|
}
|
|
|
|
for _, p := range pkgs {
|
2019-11-12 21:44:24 +09:00
|
|
|
cmd.Args = append(cmd.Args, p.PkgPath)
|
2018-03-07 22:51:37 +01:00
|
|
|
}
|
|
|
|
if err := runCmd(cmd); err != nil {
|
|
|
|
return err
|
2015-07-10 16:47:46 -06:00
|
|
|
}
|
|
|
|
|
2015-12-11 17:19:23 -05:00
|
|
|
androidDir := filepath.Join(tmpdir, "android")
|
|
|
|
|
|
|
|
// Generate binding code and java source code only when processing the first package.
|
|
|
|
for _, arch := range androidArchs {
|
|
|
|
env := androidEnv[arch]
|
2018-03-07 22:51:37 +01:00
|
|
|
// Add the generated packages to GOPATH
|
2018-03-16 11:20:43 +01:00
|
|
|
gopath := fmt.Sprintf("GOPATH=%s%c%s", tmpdir, filepath.ListSeparator, goEnv("GOPATH"))
|
2016-09-07 18:27:36 +02:00
|
|
|
env = append(env, gopath)
|
2015-12-11 17:19:23 -05:00
|
|
|
toolchain := ndk.Toolchain(arch)
|
|
|
|
|
2018-03-07 22:51:37 +01:00
|
|
|
err := goBuild(
|
|
|
|
"gobind",
|
mobile/bind: replace seq serialization with direct calls
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>
2016-02-12 18:50:33 +01:00
|
|
|
env,
|
|
|
|
"-buildmode=c-shared",
|
|
|
|
"-o="+filepath.Join(androidDir, "src/main/jniLibs/"+toolchain.abi+"/libgojni.so"),
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2015-07-10 16:47:46 -06:00
|
|
|
}
|
|
|
|
|
2018-03-07 22:51:37 +01:00
|
|
|
jsrc := filepath.Join(tmpdir, "java")
|
|
|
|
if err := buildAAR(jsrc, androidDir, pkgs, androidArchs); err != nil {
|
2017-07-31 16:13:01 +02:00
|
|
|
return err
|
|
|
|
}
|
2018-03-07 22:51:37 +01:00
|
|
|
return buildSrcJar(jsrc)
|
2015-07-10 16:47:46 -06:00
|
|
|
}
|
|
|
|
|
2018-03-07 22:51:37 +01:00
|
|
|
func buildSrcJar(src string) error {
|
2017-07-31 16:13:01 +02:00
|
|
|
var out io.Writer = ioutil.Discard
|
|
|
|
if !buildN {
|
|
|
|
ext := filepath.Ext(buildO)
|
|
|
|
f, err := os.Create(buildO[:len(buildO)-len(ext)] + "-sources.jar")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer func() {
|
|
|
|
if cerr := f.Close(); err == nil {
|
|
|
|
err = cerr
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
out = f
|
|
|
|
}
|
|
|
|
|
|
|
|
return writeJar(out, src)
|
|
|
|
}
|
|
|
|
|
2015-07-10 16:47:46 -06:00
|
|
|
// 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.
|
2019-11-12 21:44:24 +09:00
|
|
|
func buildAAR(srcDir, androidDir string, pkgs []*packages.Package, androidArchs []string) (err error) {
|
2015-07-10 16:47:46 -06:00
|
|
|
var out io.Writer = ioutil.Discard
|
|
|
|
if buildO == "" {
|
2015-11-19 10:51:29 +05:30
|
|
|
buildO = pkgs[0].Name + ".aar"
|
2015-07-10 16:47:46 -06:00
|
|
|
}
|
|
|
|
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
|
|
|
|
}
|
2015-09-23 13:22:24 -04:00
|
|
|
const manifestFmt = `<manifest xmlns:android="http://schemas.android.com/apk/res/android" package=%q>
|
2015-10-01 12:36:01 -04:00
|
|
|
<uses-sdk android:minSdkVersion="%d"/></manifest>`
|
2019-05-09 07:11:10 +08:00
|
|
|
fmt.Fprintf(w, manifestFmt, "go."+pkgs[0].Name+".gojni", buildAndroidAPI)
|
2015-07-10 16:47:46 -06:00
|
|
|
|
|
|
|
w, err = aarwcreate("proguard.txt")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
fmt.Fprintln(w, `-keep class go.** { *; }`)
|
2019-03-01 12:02:46 +01:00
|
|
|
if bindJavaPkg != "" {
|
|
|
|
fmt.Fprintln(w, `-keep class `+bindJavaPkg+`.** { *; }`)
|
|
|
|
} else {
|
|
|
|
for _, p := range pkgs {
|
|
|
|
fmt.Fprintln(w, `-keep class `+p.Name+`.** { *; }`)
|
|
|
|
}
|
|
|
|
}
|
2015-07-10 16:47:46 -06:00
|
|
|
|
|
|
|
w, err = aarwcreate("classes.jar")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2018-03-07 22:51:37 +01:00
|
|
|
if err := buildJar(w, srcDir); err != nil {
|
2015-07-10 16:47:46 -06:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2015-11-19 10:51:29 +05:30
|
|
|
files := map[string]string{}
|
|
|
|
for _, pkg := range pkgs {
|
2019-11-12 21:44:24 +09:00
|
|
|
// TODO(hajimehoshi): This works only with Go tools that assume all source files are in one directory.
|
|
|
|
// Fix this to work with other Go tools.
|
|
|
|
assetsDir := filepath.Join(filepath.Dir(pkg.GoFiles[0]), "assets")
|
2015-11-19 10:51:29 +05:30
|
|
|
assetsDirExists := false
|
|
|
|
if fi, err := os.Stat(assetsDir); err == nil {
|
|
|
|
assetsDirExists = fi.IsDir()
|
|
|
|
} else if !os.IsNotExist(err) {
|
|
|
|
return err
|
|
|
|
}
|
2015-07-10 16:47:46 -06:00
|
|
|
|
2015-11-19 10:51:29 +05:30
|
|
|
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",
|
2019-11-12 21:44:24 +09:00
|
|
|
pkg.PkgPath, name, orig)
|
2015-11-19 10:51:29 +05:30
|
|
|
}
|
2019-11-12 21:44:24 +09:00
|
|
|
files[name] = pkg.PkgPath
|
2015-11-19 10:51:29 +05:30
|
|
|
w, err := aarwcreate(name)
|
|
|
|
if err != nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
_, err = io.Copy(w, f)
|
2015-07-10 16:47:46 -06:00
|
|
|
return err
|
2015-11-19 10:51:29 +05:30
|
|
|
})
|
|
|
|
if err != nil {
|
2015-07-10 16:47:46 -06:00
|
|
|
return err
|
2015-11-19 10:51:29 +05:30
|
|
|
}
|
2015-07-10 16:47:46 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-12-11 17:19:23 -05:00
|
|
|
for _, arch := range androidArchs {
|
|
|
|
toolchain := ndk.Toolchain(arch)
|
|
|
|
lib := toolchain.abi + "/libgojni.so"
|
|
|
|
w, err = aarwcreate("jni/" + lib)
|
2015-07-10 16:47:46 -06:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2015-12-11 17:19:23 -05:00
|
|
|
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
|
|
|
|
}
|
2015-07-10 16:47:46 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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"
|
2015-11-11 23:11:29 -06:00
|
|
|
minAndroidAPI = 15
|
2015-07-10 16:47:46 -06:00
|
|
|
)
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-30 12:39:25 +02:00
|
|
|
bClspath, err := bootClasspath()
|
|
|
|
|
2015-07-10 16:47:46 -06:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
args := []string{
|
|
|
|
"-d", dst,
|
|
|
|
"-source", javacTargetVer,
|
|
|
|
"-target", javacTargetVer,
|
2016-09-30 12:39:25 +02:00
|
|
|
"-bootclasspath", bClspath,
|
|
|
|
}
|
|
|
|
if bindClasspath != "" {
|
|
|
|
args = append(args, "-classpath", bindClasspath)
|
2015-07-10 16:47:46 -06:00
|
|
|
}
|
2016-09-30 12:39:25 +02:00
|
|
|
|
2015-07-10 16:47:46 -06:00
|
|
|
args = append(args, srcFiles...)
|
|
|
|
|
|
|
|
javac := exec.Command("javac", args...)
|
|
|
|
javac.Dir = srcDir
|
2015-07-19 21:29:35 -04:00
|
|
|
if err := runCmd(javac); err != nil {
|
|
|
|
return err
|
2015-07-10 16:47:46 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
if buildX {
|
|
|
|
printcmd("jar c -C %s .", dst)
|
|
|
|
}
|
2017-07-31 16:13:01 +02:00
|
|
|
return writeJar(w, dst)
|
|
|
|
}
|
|
|
|
|
|
|
|
func writeJar(w io.Writer, dir string) error {
|
2015-07-10 16:47:46 -06:00
|
|
|
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)
|
|
|
|
|
2017-07-31 16:13:01 +02:00
|
|
|
err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
2015-07-10 16:47:46 -06:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if info.IsDir() {
|
|
|
|
return nil
|
|
|
|
}
|
2017-07-31 16:13:01 +02:00
|
|
|
out, err := jarwcreate(filepath.ToSlash(path[len(dir)+1:]))
|
2015-07-10 16:47:46 -06:00
|
|
|
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 {
|
2019-05-09 07:11:10 +08:00
|
|
|
return "", fmt.Errorf("failed to find android SDK platform (API level: %d): %v", buildAndroidAPI, err)
|
2015-07-10 16:47:46 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
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-"):])
|
2019-05-09 07:11:10 +08:00
|
|
|
if err != nil || n < buildAndroidAPI {
|
2015-07-10 16:47:46 -06:00
|
|
|
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 {
|
2019-05-09 07:11:10 +08:00
|
|
|
return "", fmt.Errorf("failed to find android SDK platform (API level: %d) in %s",
|
|
|
|
buildAndroidAPI, sdkDir.Name())
|
2015-07-10 16:47:46 -06:00
|
|
|
}
|
|
|
|
return apiPath, nil
|
|
|
|
}
|