A previous CL switched gobind to use the "source" importer so go files using cgo can be type checked. However, CGO_ENABLED=1 also have to be passed in for such files not be ignored. Also add -tags ios to the gomobile test output missing from CL 99777. Fixes golang/go#24941 Change-Id: I868469af3061b82ab592899e365a8a90a0333e58 Reviewed-on: https://go-review.googlesource.com/108336 Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
393 lines
9.0 KiB
Go
393 lines
9.0 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"
|
|
"errors"
|
|
"fmt"
|
|
"go/build"
|
|
"io"
|
|
"io/ioutil"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
func goAndroidBind(gobind string, 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)")
|
|
}
|
|
|
|
if !hasNDK() {
|
|
return errors.New("no Android NDK path is set. Please run gomobile init with the ndk-bundle installed through the Android SDK manager or with the -ndk flag set.")
|
|
}
|
|
|
|
// Run gobind to generate the bindings
|
|
cmd := exec.Command(
|
|
gobind,
|
|
"-lang=go,java",
|
|
"-outdir="+tmpdir,
|
|
)
|
|
cmd.Env = append(cmd.Env, "GOOS=android")
|
|
cmd.Env = append(cmd.Env, "CGO_ENABLED=1")
|
|
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 {
|
|
cmd.Args = append(cmd.Args, p.ImportPath)
|
|
}
|
|
if err := runCmd(cmd); err != nil {
|
|
return err
|
|
}
|
|
|
|
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]
|
|
// Add the generated packages to GOPATH
|
|
gopath := fmt.Sprintf("GOPATH=%s%c%s", tmpdir, filepath.ListSeparator, goEnv("GOPATH"))
|
|
env = append(env, gopath)
|
|
toolchain := ndk.Toolchain(arch)
|
|
|
|
err := goBuild(
|
|
"gobind",
|
|
env,
|
|
"-buildmode=c-shared",
|
|
"-o="+filepath.Join(androidDir, "src/main/jniLibs/"+toolchain.abi+"/libgojni.so"),
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
jsrc := filepath.Join(tmpdir, "java")
|
|
if err := buildAAR(jsrc, androidDir, pkgs, androidArchs); err != nil {
|
|
return err
|
|
}
|
|
return buildSrcJar(jsrc)
|
|
}
|
|
|
|
func buildSrcJar(src string) error {
|
|
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)
|
|
}
|
|
|
|
// 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(srcDir, 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
|
|
}
|
|
if err := buildJar(w, srcDir); 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
|
|
}
|
|
}
|
|
|
|
bClspath, err := bootClasspath()
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
args := []string{
|
|
"-d", dst,
|
|
"-source", javacTargetVer,
|
|
"-target", javacTargetVer,
|
|
"-bootclasspath", bClspath,
|
|
}
|
|
if bindClasspath != "" {
|
|
args = append(args, "-classpath", bindClasspath)
|
|
}
|
|
|
|
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)
|
|
}
|
|
return writeJar(w, dst)
|
|
}
|
|
|
|
func writeJar(w io.Writer, dir string) error {
|
|
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(dir, 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(dir)+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
|
|
}
|