cmd/gomobile: reorganize build logic
The goal here is to remove several inconsistencies between -target=android and -target=ios support, along with making the flow of the command follow the path you might expect given a certain set of flags, and preparing for `gomobile bind` support of ios. In particular, building non-main packages now works with both targets and the initialization of global build state is clearer. The reorg also is designed around an nm trick I thought of yesterday to do better package import scanning without a slow all-file scan. This will give better detection of x/mobile/app and x/mobile/exp/audio/al packages. There's a TODO about it, and I'll do it in a future CL. Tested with: go test golang.org/x/mobile/cmd/gomobile gomobile init gomobile bind golang.org/x/mobile/asset go test golang.org/x/mobile/bind/java gomobile build -target=ios golang.org/x/mobile/example/basic gomobile build -target=ios golang.org/x/mobile/gl gomobile build -target=android golang.org/x/mobile/gl gomobile build -target=android golang.org/x/mobile/example/basic (Along with manual testing of basic on an android device.) That might make a pretty good _test.go. Change-Id: I41230008c3c15db25a11c33b9eaca4abada9f411 Reviewed-on: https://go-review.googlesource.com/12051 Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
This commit is contained in:
parent
7fb893ba43
commit
e5193c59b3
@ -21,6 +21,7 @@ public class Seq {
|
|||||||
Class.forName("go.LoadJNI");
|
Class.forName("go.LoadJNI");
|
||||||
} catch (ClassNotFoundException e) {
|
} catch (ClassNotFoundException e) {
|
||||||
// Ignore, assume the user will load JNI for it.
|
// Ignore, assume the user will load JNI for it.
|
||||||
|
Log.w("GoSeq", "LoadJNI class not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
initSeq();
|
initSeq();
|
||||||
@ -239,7 +240,7 @@ public class Seq {
|
|||||||
if (refnum <= 0) {
|
if (refnum <= 0) {
|
||||||
// We don't keep track of the Go object.
|
// We don't keep track of the Go object.
|
||||||
// This must not happen.
|
// This must not happen.
|
||||||
Log.wtf("Seq", "dec request for Go object "+ refnum);
|
Log.wtf("GoSeq", "dec request for Go object "+ refnum);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Java objects are removed on request of Go.
|
// Java objects are removed on request of Go.
|
||||||
|
@ -5,8 +5,6 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"archive/zip"
|
|
||||||
"bytes"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"go/ast"
|
"go/ast"
|
||||||
@ -17,11 +15,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"text/template"
|
|
||||||
"unicode"
|
"unicode"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
@ -30,7 +24,7 @@ import (
|
|||||||
"golang.org/x/tools/go/types"
|
"golang.org/x/tools/go/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ctx, pkg, ndkccpath, tmpdir in build.go
|
// ctx, pkg, tmpdir in build.go
|
||||||
|
|
||||||
var cmdBind = &command{
|
var cmdBind = &command{
|
||||||
run: runBind,
|
run: runBind,
|
||||||
@ -69,18 +63,20 @@ For documentation, see 'go help build'.
|
|||||||
}
|
}
|
||||||
|
|
||||||
func runBind(cmd *command) error {
|
func runBind(cmd *command) error {
|
||||||
cwd, err := os.Getwd()
|
cleanup, err := envInit()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return err
|
||||||
}
|
}
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
args := cmd.flag.Args()
|
args := cmd.flag.Args()
|
||||||
|
|
||||||
var bindPkg *build.Package
|
var pkg *build.Package
|
||||||
switch len(args) {
|
switch len(args) {
|
||||||
case 0:
|
case 0:
|
||||||
bindPkg, err = ctx.ImportDir(cwd, build.ImportComment)
|
pkg, err = ctx.ImportDir(cwd, build.ImportComment)
|
||||||
case 1:
|
case 1:
|
||||||
bindPkg, err = ctx.Import(args[0], cwd, build.ImportComment)
|
pkg, err = ctx.Import(args[0], cwd, build.ImportComment)
|
||||||
default:
|
default:
|
||||||
cmd.usage()
|
cmd.usage()
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
@ -91,89 +87,14 @@ func runBind(cmd *command) error {
|
|||||||
|
|
||||||
switch buildTarget {
|
switch buildTarget {
|
||||||
case "android":
|
case "android":
|
||||||
// implementation is below
|
return goAndroidBind(pkg)
|
||||||
case "ios":
|
case "ios":
|
||||||
return fmt.Errorf(`-target=ios not yet supported`)
|
return fmt.Errorf(`-target=ios not yet supported`)
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf(`unknown -target, %q.`, buildTarget)
|
return fmt.Errorf(`unknown -target, %q.`, buildTarget)
|
||||||
}
|
}
|
||||||
|
|
||||||
if sdkDir := os.Getenv("ANDROID_HOME"); sdkDir == "" {
|
|
||||||
return fmt.Errorf("this command requires ANDROID_HOME environment variable (path to the Android SDK)")
|
|
||||||
}
|
|
||||||
|
|
||||||
if buildN {
|
|
||||||
tmpdir = "$WORK"
|
|
||||||
} else {
|
|
||||||
tmpdir, err = ioutil.TempDir("", "gomobile-bind-work-")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
defer removeAll(tmpdir)
|
|
||||||
if buildX {
|
|
||||||
fmt.Fprintln(os.Stderr, "WORK="+tmpdir)
|
|
||||||
}
|
|
||||||
|
|
||||||
binder, err := newBinder(bindPkg)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := binder.GenGo(tmpdir); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
mainFile := filepath.Join(tmpdir, "androidlib/main.go")
|
|
||||||
err = writeFile(mainFile, func(w io.Writer) error {
|
|
||||||
return androidMainTmpl.Execute(w, "../go_"+binder.pkg.Name())
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to create the main package for android: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
androidDir := filepath.Join(tmpdir, "android")
|
|
||||||
|
|
||||||
err = goAndroidBuild(mainFile, filepath.Join(androidDir, "src/main/jniLibs/armeabi-v7a/libgojni.so"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
p, err := ctx.Import("golang.org/x/mobile/app", cwd, build.ImportComment)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf(`"golang.org/x/mobile/app" is not found; run go get golang.org/x/mobile/app`)
|
|
||||||
}
|
|
||||||
repo := filepath.Clean(filepath.Join(p.Dir, "..")) // golang.org/x/mobile directory.
|
|
||||||
|
|
||||||
// TODO(crawshaw): use a better package path derived from the go package.
|
|
||||||
if err := binder.GenJava(filepath.Join(androidDir, "src/main/java/go/"+binder.pkg.Name())); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
dst := filepath.Join(androidDir, "src/main/java/go/LoadJNI.java")
|
|
||||||
if err := ioutil.WriteFile(dst, []byte(loadSrc), 0664); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
src := filepath.Join(repo, "bind/java/Seq.java")
|
|
||||||
dst = filepath.Join(androidDir, "src/main/java/go/Seq.java")
|
|
||||||
rm(dst)
|
|
||||||
if err := symlink(src, dst); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return buildAAR(androidDir, bindPkg)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var loadSrc = `package go;
|
|
||||||
|
|
||||||
public class LoadJNI {
|
|
||||||
static {
|
|
||||||
System.loadLibrary("gojni");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
type binder struct {
|
type binder struct {
|
||||||
files []*ast.File
|
files []*ast.File
|
||||||
fset *token.FileSet
|
fset *token.FileSet
|
||||||
@ -294,300 +215,3 @@ func newBinder(bindPkg *build.Package) (*binder, error) {
|
|||||||
}
|
}
|
||||||
return b, nil
|
return b, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var androidMainTmpl = template.Must(template.New("android.go").Parse(`
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
_ "golang.org/x/mobile/bind/java"
|
|
||||||
_ "{{.}}"
|
|
||||||
)
|
|
||||||
|
|
||||||
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, pkg *build.Package) (err error) {
|
|
||||||
var out io.Writer = ioutil.Discard
|
|
||||||
if buildO == "" {
|
|
||||||
buildO = pkg.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 />`
|
|
||||||
fmt.Fprintf(w, manifestFmt, "go."+pkg.Name+".gojni")
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
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:]
|
|
||||||
w, err := aarwcreate(name)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
_, err = io.Copy(w, f)
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lib := "armeabi-v7a/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 = 9
|
|
||||||
)
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
defer removeAll(dst)
|
|
||||||
|
|
||||||
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...)
|
|
||||||
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
javac := exec.Command("javac", args...)
|
|
||||||
javac.Dir = srcDir
|
|
||||||
if buildV {
|
|
||||||
javac.Stdout = os.Stdout
|
|
||||||
javac.Stderr = os.Stderr
|
|
||||||
} else {
|
|
||||||
javac.Stdout = buf
|
|
||||||
javac.Stderr = buf
|
|
||||||
}
|
|
||||||
if buildX {
|
|
||||||
printcmd("%s", strings.Join(javac.Args, " "))
|
|
||||||
}
|
|
||||||
if !buildN {
|
|
||||||
if err := javac.Run(); err != nil {
|
|
||||||
buf.WriteTo(xout)
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
386
cmd/gomobile/bind_androidapp.go
Normal file
386
cmd/gomobile/bind_androidapp.go
Normal file
@ -0,0 +1,386 @@
|
|||||||
|
// 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"
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"go/build"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
)
|
||||||
|
|
||||||
|
func goAndroidBind(pkg *build.Package) error {
|
||||||
|
if sdkDir := os.Getenv("ANDROID_HOME"); sdkDir == "" {
|
||||||
|
return fmt.Errorf("this command requires ANDROID_HOME environment variable (path to the Android SDK)")
|
||||||
|
}
|
||||||
|
|
||||||
|
binder, err := newBinder(pkg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := binder.GenGo(tmpdir); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
mainFile := filepath.Join(tmpdir, "androidlib/main.go")
|
||||||
|
err = writeFile(mainFile, func(w io.Writer) error {
|
||||||
|
return androidMainTmpl.Execute(w, "../go_"+binder.pkg.Name())
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create the main package for android: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
androidDir := filepath.Join(tmpdir, "android")
|
||||||
|
|
||||||
|
err = goBuild(
|
||||||
|
mainFile,
|
||||||
|
androidArmEnv,
|
||||||
|
"-buildmode=c-shared",
|
||||||
|
"-o="+filepath.Join(androidDir, "src/main/jniLibs/armeabi-v7a/libgojni.so"),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return 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.
|
||||||
|
|
||||||
|
// TODO(crawshaw): use a better package path derived from the go package.
|
||||||
|
if err := binder.GenJava(filepath.Join(androidDir, "src/main/java/go/"+binder.pkg.Name())); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
dst := filepath.Join(androidDir, "src/main/java/go/LoadJNI.java")
|
||||||
|
if err := ioutil.WriteFile(dst, []byte(loadSrc), 0664); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
src := filepath.Join(repo, "bind/java/Seq.java")
|
||||||
|
dst = filepath.Join(androidDir, "src/main/java/go/Seq.java")
|
||||||
|
rm(dst)
|
||||||
|
if err := symlink(src, dst); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return buildAAR(androidDir, pkg)
|
||||||
|
}
|
||||||
|
|
||||||
|
var loadSrc = `package go;
|
||||||
|
|
||||||
|
public class LoadJNI {
|
||||||
|
static {
|
||||||
|
System.loadLibrary("gojni");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
var androidMainTmpl = template.Must(template.New("android.go").Parse(`
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "golang.org/x/mobile/bind/java"
|
||||||
|
_ "{{.}}"
|
||||||
|
)
|
||||||
|
|
||||||
|
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, pkg *build.Package) (err error) {
|
||||||
|
var out io.Writer = ioutil.Discard
|
||||||
|
if buildO == "" {
|
||||||
|
buildO = pkg.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 />`
|
||||||
|
fmt.Fprintf(w, manifestFmt, "go."+pkg.Name+".gojni")
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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:]
|
||||||
|
w, err := aarwcreate(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
_, err = io.Copy(w, f)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lib := "armeabi-v7a/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 = 9
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defer removeAll(dst)
|
||||||
|
|
||||||
|
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...)
|
||||||
|
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
javac := exec.Command("javac", args...)
|
||||||
|
javac.Dir = srcDir
|
||||||
|
if buildV {
|
||||||
|
javac.Stdout = os.Stdout
|
||||||
|
javac.Stderr = os.Stderr
|
||||||
|
} else {
|
||||||
|
javac.Stdout = buf
|
||||||
|
javac.Stderr = buf
|
||||||
|
}
|
||||||
|
if buildX {
|
||||||
|
printcmd("%s", strings.Join(javac.Args, " "))
|
||||||
|
}
|
||||||
|
if !buildN {
|
||||||
|
if err := javac.Run(); err != nil {
|
||||||
|
buf.WriteTo(xout)
|
||||||
|
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
|
||||||
|
}
|
@ -6,26 +6,18 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/x509"
|
|
||||||
"encoding/pem"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"go/build"
|
"go/build"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path"
|
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ctx = build.Default
|
var ctx = build.Default
|
||||||
var pkg *build.Package
|
var pkg *build.Package // TODO(crawshaw): remove global pkg variable
|
||||||
var gomobilepath string // $GOPATH/pkg/gomobile
|
|
||||||
var ndkccpath string // $GOPATH/pkg/gomobile/android-{{.NDK}}
|
|
||||||
var tmpdir string
|
var tmpdir string
|
||||||
|
|
||||||
var cmdBuild = &command{
|
var cmdBuild = &command{
|
||||||
@ -62,10 +54,12 @@ For documentation, see 'go help build'.
|
|||||||
}
|
}
|
||||||
|
|
||||||
func runBuild(cmd *command) (err error) {
|
func runBuild(cmd *command) (err error) {
|
||||||
cwd, err := os.Getwd()
|
cleanup, err := envInit()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return err
|
||||||
}
|
}
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
args := cmd.flag.Args()
|
args := cmd.flag.Args()
|
||||||
|
|
||||||
switch len(args) {
|
switch len(args) {
|
||||||
@ -81,214 +75,43 @@ func runBuild(cmd *command) (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if pkg.Name != "main" && buildO != "" {
|
||||||
|
return fmt.Errorf("cannot set -o when building non-main package")
|
||||||
|
}
|
||||||
|
|
||||||
switch buildTarget {
|
switch buildTarget {
|
||||||
case "android":
|
case "android":
|
||||||
// implementation is below
|
if pkg.Name != "main" {
|
||||||
|
return goBuild(pkg.ImportPath, androidArmEnv)
|
||||||
|
}
|
||||||
|
if err := goAndroidBuild(pkg); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
case "ios":
|
case "ios":
|
||||||
if runtime.GOOS == "darwin" {
|
if runtime.GOOS != "darwin" {
|
||||||
if pkg.Name != "main" {
|
return fmt.Errorf("-target=ios requires darwin host")
|
||||||
return fmt.Errorf("cannot build non-main packages")
|
}
|
||||||
}
|
if pkg.Name != "main" {
|
||||||
if err := importsApp(pkg); err != nil {
|
if err := goBuild(pkg.ImportPath, darwinArmEnv); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return goIOSBuild(pkg.ImportPath)
|
return goBuild(pkg.ImportPath, darwinArm64Env)
|
||||||
|
}
|
||||||
|
if err := goIOSBuild(pkg); err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
return fmt.Errorf("-target=ios requires darwin host")
|
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf(`unknown -target, %q.`, buildTarget)
|
return fmt.Errorf(`unknown -target, %q.`, buildTarget)
|
||||||
}
|
}
|
||||||
|
|
||||||
if pkg.Name != "main" {
|
// TODO(crawshaw): This is an incomplete package scan.
|
||||||
// Not an app, don't build a final package.
|
// A complete package scan would be too expensive. Instead,
|
||||||
return goAndroidBuild(pkg.ImportPath, "")
|
// fake it. After the binary is built, scan its symbols
|
||||||
}
|
// with nm and look for the app and al packages.
|
||||||
|
|
||||||
if err := importsApp(pkg); err != nil {
|
if err := importsApp(pkg); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if buildN {
|
|
||||||
tmpdir = "$WORK"
|
|
||||||
} else {
|
|
||||||
tmpdir, err = ioutil.TempDir("", "gobuildapk-work-")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
defer removeAll(tmpdir)
|
|
||||||
if buildX {
|
|
||||||
fmt.Fprintln(xout, "WORK="+tmpdir)
|
|
||||||
}
|
|
||||||
|
|
||||||
libName := path.Base(pkg.ImportPath)
|
|
||||||
manifestData, err := ioutil.ReadFile(filepath.Join(pkg.Dir, "AndroidManifest.xml"))
|
|
||||||
if err != nil {
|
|
||||||
if !os.IsNotExist(err) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
buf.WriteString(`<?xml version="1.0" encoding="utf-8"?>`)
|
|
||||||
err := manifestTmpl.Execute(buf, manifestTmplData{
|
|
||||||
// TODO(crawshaw): a better package path.
|
|
||||||
JavaPkgPath: "org.golang.todo." + libName,
|
|
||||||
Name: libName,
|
|
||||||
LibName: libName,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
manifestData = buf.Bytes()
|
|
||||||
if buildV {
|
|
||||||
fmt.Fprintf(os.Stderr, "generated AndroidManifest.xml:\n%s\n", manifestData)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
libName, err = manifestLibName(manifestData)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
libPath := filepath.Join(tmpdir, "lib"+libName+".so")
|
|
||||||
|
|
||||||
if err := goAndroidBuild(pkg.ImportPath, libPath); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
block, _ := pem.Decode([]byte(debugCert))
|
|
||||||
if block == nil {
|
|
||||||
return errors.New("no debug cert")
|
|
||||||
}
|
|
||||||
privKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if buildO == "" {
|
|
||||||
buildO = filepath.Base(pkg.Dir) + ".apk"
|
|
||||||
}
|
|
||||||
if !strings.HasSuffix(buildO, ".apk") {
|
|
||||||
return fmt.Errorf("output file name %q does not end in '.apk'", buildO)
|
|
||||||
}
|
|
||||||
var out io.Writer
|
|
||||||
if !buildN {
|
|
||||||
f, err := os.Create(buildO)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
if cerr := f.Close(); err == nil {
|
|
||||||
err = cerr
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
out = f
|
|
||||||
}
|
|
||||||
|
|
||||||
var apkw *Writer
|
|
||||||
if !buildN {
|
|
||||||
apkw = NewWriter(out, privKey)
|
|
||||||
}
|
|
||||||
apkwcreate := func(name string) (io.Writer, error) {
|
|
||||||
if buildV {
|
|
||||||
fmt.Fprintf(os.Stderr, "apk: %s\n", name)
|
|
||||||
}
|
|
||||||
if buildN {
|
|
||||||
return ioutil.Discard, nil
|
|
||||||
}
|
|
||||||
return apkw.Create(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
w, err := apkwcreate("AndroidManifest.xml")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if _, err := w.Write(manifestData); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
w, err = apkwcreate("lib/armeabi/lib" + libName + ".so")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !buildN {
|
|
||||||
r, err := os.Open(libPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if _, err := io.Copy(w, r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
importsAL := pkgImportsAL(pkg)
|
|
||||||
if importsAL {
|
|
||||||
alDir := filepath.Join(ndkccpath, "openal/lib")
|
|
||||||
filepath.Walk(alDir, func(path string, info os.FileInfo, err error) error {
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if info.IsDir() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
name := "lib/" + path[len(alDir)+1:]
|
|
||||||
w, err := apkwcreate(name)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !buildN {
|
|
||||||
f, err := os.Open(path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
_, err = io.Copy(w, f)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add any assets.
|
|
||||||
assetsDir := filepath.Join(pkg.Dir, "assets")
|
|
||||||
assetsDirExists := true
|
|
||||||
fi, err := os.Stat(assetsDir)
|
|
||||||
if err != nil {
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
assetsDirExists = false
|
|
||||||
} else {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
assetsDirExists = fi.IsDir()
|
|
||||||
}
|
|
||||||
if assetsDirExists {
|
|
||||||
filepath.Walk(assetsDir, func(path string, info os.FileInfo, err error) error {
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if info.IsDir() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
name := "assets/" + path[len(assetsDir)+1:]
|
|
||||||
w, err := apkwcreate(name)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
f, err := os.Open(path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
_, err = io.Copy(w, f)
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: add gdbserver to apk?
|
|
||||||
|
|
||||||
if !buildN {
|
|
||||||
if err := apkw.Close(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -353,114 +176,9 @@ func addBuildFlagsNVX(cmd *command) {
|
|||||||
cmd.flag.BoolVar(&buildX, "x", false, "")
|
cmd.flag.BoolVar(&buildX, "x", false, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
// goAndroidBuild builds a package.
|
type binInfo struct {
|
||||||
// If libPath is specified then it builds as a shared library.
|
hasPkgApp bool
|
||||||
func goAndroidBuild(src, libPath string) error {
|
hasPkgAL bool
|
||||||
version, err := goVersion()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
gopath := goEnv("GOPATH")
|
|
||||||
for _, p := range filepath.SplitList(gopath) {
|
|
||||||
gomobilepath = filepath.Join(p, "pkg", "gomobile")
|
|
||||||
if _, err = os.Stat(gomobilepath); err == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err != nil || gomobilepath == "" {
|
|
||||||
return errors.New("toolchain not installed, run:\n\tgomobile init")
|
|
||||||
}
|
|
||||||
verpath := filepath.Join(gomobilepath, "version")
|
|
||||||
installedVersion, err := ioutil.ReadFile(verpath)
|
|
||||||
if err != nil {
|
|
||||||
return errors.New("toolchain partially installed, run:\n\tgomobile init")
|
|
||||||
}
|
|
||||||
if !bytes.Equal(installedVersion, version) {
|
|
||||||
return errors.New("toolchain out of date, run:\n\tgomobile init")
|
|
||||||
}
|
|
||||||
|
|
||||||
ndkccpath = filepath.Join(gomobilepath, "android-"+ndkVersion)
|
|
||||||
ndkccbin := filepath.Join(ndkccpath, "arm", "bin")
|
|
||||||
if buildX {
|
|
||||||
fmt.Fprintln(xout, "GOMOBILE="+gomobilepath)
|
|
||||||
}
|
|
||||||
|
|
||||||
gocmd := exec.Command(
|
|
||||||
`go`,
|
|
||||||
`build`,
|
|
||||||
`-tags=`+strconv.Quote(strings.Join(ctx.BuildTags, ",")),
|
|
||||||
)
|
|
||||||
if buildV {
|
|
||||||
gocmd.Args = append(gocmd.Args, "-v")
|
|
||||||
}
|
|
||||||
if buildI {
|
|
||||||
gocmd.Args = append(gocmd.Args, "-i")
|
|
||||||
}
|
|
||||||
if buildX {
|
|
||||||
gocmd.Args = append(gocmd.Args, "-x")
|
|
||||||
}
|
|
||||||
if libPath == "" {
|
|
||||||
if buildO != "" {
|
|
||||||
gocmd.Args = append(gocmd.Args, `-o`, buildO)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
gocmd.Args = append(gocmd.Args, "-buildmode=c-shared", "-o", libPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
gocmd.Args = append(gocmd.Args, src)
|
|
||||||
|
|
||||||
gocmd.Stdout = os.Stdout
|
|
||||||
gocmd.Stderr = os.Stderr
|
|
||||||
gocmd.Env = []string{
|
|
||||||
`GOOS=android`,
|
|
||||||
`GOARCH=arm`,
|
|
||||||
`GOARM=7`,
|
|
||||||
`CGO_ENABLED=1`,
|
|
||||||
`CC=` + filepath.Join(ndkccbin, "arm-linux-androideabi-gcc"),
|
|
||||||
`CXX=` + filepath.Join(ndkccbin, "arm-linux-androideabi-g++"),
|
|
||||||
`GOGCCFLAGS="-fPIC -marm -pthread -fmessage-length=0"`,
|
|
||||||
`GOROOT=` + goEnv("GOROOT"),
|
|
||||||
`GOPATH=` + gopath,
|
|
||||||
}
|
|
||||||
if buildX {
|
|
||||||
printcmd("%s", strings.Join(gocmd.Env, " ")+" "+strings.Join(gocmd.Args, " "))
|
|
||||||
}
|
|
||||||
if !buildN {
|
|
||||||
gocmd.Env = environ(gocmd.Env)
|
|
||||||
if err := gocmd.Run(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var importsALPkg = make(map[string]struct{})
|
|
||||||
|
|
||||||
// pkgImportsAL returns true if the given package or one of its
|
|
||||||
// dependencies imports the x/mobile/exp/audio/al package.
|
|
||||||
func pkgImportsAL(pkg *build.Package) bool {
|
|
||||||
for _, path := range pkg.Imports {
|
|
||||||
if path == "C" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if _, ok := importsALPkg[path]; ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
importsALPkg[path] = struct{}{}
|
|
||||||
if strings.HasPrefix(path, "golang.org/x/mobile/exp/audio/al") {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
dPkg, err := ctx.Import(path, "", build.ImportComment)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "error reading OpenAL library: %v", err)
|
|
||||||
os.Exit(2)
|
|
||||||
}
|
|
||||||
if pkgImportsAL(dPkg) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -476,38 +194,6 @@ func init() {
|
|||||||
addBuildFlagsNVX(cmdBind)
|
addBuildFlagsNVX(cmdBind)
|
||||||
}
|
}
|
||||||
|
|
||||||
// A random uninteresting private key.
|
|
||||||
// Must be consistent across builds so newer app versions can be installed.
|
|
||||||
const debugCert = `
|
|
||||||
-----BEGIN RSA PRIVATE KEY-----
|
|
||||||
MIIEowIBAAKCAQEAy6ItnWZJ8DpX9R5FdWbS9Kr1U8Z7mKgqNByGU7No99JUnmyu
|
|
||||||
NQ6Uy6Nj0Gz3o3c0BXESECblOC13WdzjsH1Pi7/L9QV8jXOXX8cvkG5SJAyj6hcO
|
|
||||||
LOapjDiN89NXjXtyv206JWYvRtpexyVrmHJgRAw3fiFI+m4g4Qop1CxcIF/EgYh7
|
|
||||||
rYrqh4wbCM1OGaCleQWaOCXxZGm+J5YNKQcWpjZRrDrb35IZmlT0bK46CXUKvCqK
|
|
||||||
x7YXHgfhC8ZsXCtsScKJVHs7gEsNxz7A0XoibFw6DoxtjKzUCktnT0w3wxdY7OTj
|
|
||||||
9AR8mobFlM9W3yirX8TtwekWhDNTYEu8dwwykwIDAQABAoIBAA2hjpIhvcNR9H9Z
|
|
||||||
BmdEecydAQ0ZlT5zy1dvrWI++UDVmIp+Ve8BSd6T0mOqV61elmHi3sWsBN4M1Rdz
|
|
||||||
3N38lW2SajG9q0fAvBpSOBHgAKmfGv3Ziz5gNmtHgeEXfZ3f7J95zVGhlHqWtY95
|
|
||||||
JsmuplkHxFMyITN6WcMWrhQg4A3enKLhJLlaGLJf9PeBrvVxHR1/txrfENd2iJBH
|
|
||||||
FmxVGILL09fIIktJvoScbzVOneeWXj5vJGzWVhB17DHBbANGvVPdD5f+k/s5aooh
|
|
||||||
hWAy/yLKocr294C4J+gkO5h2zjjjSGcmVHfrhlXQoEPX+iW1TGoF8BMtl4Llc+jw
|
|
||||||
lKWKfpECgYEA9C428Z6CvAn+KJ2yhbAtuRo41kkOVoiQPtlPeRYs91Pq4+NBlfKO
|
|
||||||
2nWLkyavVrLx4YQeCeaEU2Xoieo9msfLZGTVxgRlztylOUR+zz2FzDBYGicuUD3s
|
|
||||||
EqC0Wv7tiX6dumpWyOcVVLmR9aKlOUzA9xemzIsWUwL3PpyONhKSq7kCgYEA1X2F
|
|
||||||
f2jKjoOVzglhtuX4/SP9GxS4gRf9rOQ1Q8DzZhyH2LZ6Dnb1uEQvGhiqJTU8CXxb
|
|
||||||
7odI0fgyNXq425Nlxc1Tu0G38TtJhwrx7HWHuFcbI/QpRtDYLWil8Zr7Q3BT9rdh
|
|
||||||
moo4m937hLMvqOG9pyIbyjOEPK2WBCtKW5yabqsCgYEAu9DkUBr1Qf+Jr+IEU9I8
|
|
||||||
iRkDSMeusJ6gHMd32pJVCfRRQvIlG1oTyTMKpafmzBAd/rFpjYHynFdRcutqcShm
|
|
||||||
aJUq3QG68U9EAvWNeIhA5tr0mUEz3WKTt4xGzYsyWES8u4tZr3QXMzD9dOuinJ1N
|
|
||||||
+4EEumXtSPKKDG3M8Qh+KnkCgYBUEVSTYmF5EynXc2xOCGsuy5AsrNEmzJqxDUBI
|
|
||||||
SN/P0uZPmTOhJIkIIZlmrlW5xye4GIde+1jajeC/nG7U0EsgRAV31J4pWQ5QJigz
|
|
||||||
0+g419wxIUFryGuIHhBSfpP472+w1G+T2mAGSLh1fdYDq7jx6oWE7xpghn5vb9id
|
|
||||||
EKLjdwKBgBtz9mzbzutIfAW0Y8F23T60nKvQ0gibE92rnUbjPnw8HjL3AZLU05N+
|
|
||||||
cSL5bhq0N5XHK77sscxW9vXjG0LJMXmFZPp9F6aV6ejkMIXyJ/Yz/EqeaJFwilTq
|
|
||||||
Mc6xR47qkdzu0dQ1aPm4XD7AWDtIvPo/GG2DKOucLBbQc2cOWtKS
|
|
||||||
-----END RSA PRIVATE KEY-----
|
|
||||||
`
|
|
||||||
|
|
||||||
// environ merges os.Environ and the given "key=value" pairs.
|
// environ merges os.Environ and the given "key=value" pairs.
|
||||||
func environ(kv []string) []string {
|
func environ(kv []string) []string {
|
||||||
envs := map[string]string{}
|
envs := map[string]string{}
|
||||||
@ -542,3 +228,48 @@ func environ(kv []string) []string {
|
|||||||
}
|
}
|
||||||
return new
|
return new
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func goBuild(src string, env []string, args ...string) error {
|
||||||
|
cmd := exec.Command(
|
||||||
|
`go`,
|
||||||
|
`build`,
|
||||||
|
`-tags=`+strconv.Quote(strings.Join(ctx.BuildTags, ",")),
|
||||||
|
)
|
||||||
|
if buildV {
|
||||||
|
cmd.Args = append(cmd.Args, "-v")
|
||||||
|
}
|
||||||
|
if buildI {
|
||||||
|
cmd.Args = append(cmd.Args, "-i")
|
||||||
|
}
|
||||||
|
if buildX {
|
||||||
|
cmd.Args = append(cmd.Args, "-x")
|
||||||
|
}
|
||||||
|
cmd.Args = append(cmd.Args, args...)
|
||||||
|
cmd.Args = append(cmd.Args, src)
|
||||||
|
cmd.Env = append(cmd.Env, env...)
|
||||||
|
cmd.Env = append(cmd.Env,
|
||||||
|
`CGO_ENABLED=1`,
|
||||||
|
`GOROOT=`+goEnv("GOROOT"),
|
||||||
|
`GOPATH=`+goEnv("GOPATH"),
|
||||||
|
)
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
buf.WriteByte('\n')
|
||||||
|
if buildV {
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
} else {
|
||||||
|
cmd.Stdout = buf
|
||||||
|
cmd.Stderr = buf
|
||||||
|
}
|
||||||
|
|
||||||
|
if buildX {
|
||||||
|
printcmd("%s", strings.Join(cmd.Env, " ")+" "+strings.Join(cmd.Args, " "))
|
||||||
|
}
|
||||||
|
if !buildN {
|
||||||
|
cmd.Env = environ(cmd.Env)
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
return fmt.Errorf("go build failed: %v%s", err, buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
260
cmd/gomobile/build_androidapp.go
Normal file
260
cmd/gomobile/build_androidapp.go
Normal file
@ -0,0 +1,260 @@
|
|||||||
|
// 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 (
|
||||||
|
"bytes"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/pem"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"go/build"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func goAndroidBuild(pkg *build.Package) error {
|
||||||
|
libName := path.Base(pkg.ImportPath)
|
||||||
|
manifestData, err := ioutil.ReadFile(filepath.Join(pkg.Dir, "AndroidManifest.xml"))
|
||||||
|
if err != nil {
|
||||||
|
if !os.IsNotExist(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
buf.WriteString(`<?xml version="1.0" encoding="utf-8"?>`)
|
||||||
|
err := manifestTmpl.Execute(buf, manifestTmplData{
|
||||||
|
// TODO(crawshaw): a better package path.
|
||||||
|
JavaPkgPath: "org.golang.todo." + libName,
|
||||||
|
Name: libName,
|
||||||
|
LibName: libName,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
manifestData = buf.Bytes()
|
||||||
|
if buildV {
|
||||||
|
fmt.Fprintf(os.Stderr, "generated AndroidManifest.xml:\n%s\n", manifestData)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
libName, err = manifestLibName(manifestData)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
libPath := filepath.Join(tmpdir, "lib"+libName+".so")
|
||||||
|
|
||||||
|
err = goBuild(
|
||||||
|
pkg.ImportPath,
|
||||||
|
androidArmEnv,
|
||||||
|
"-buildmode=c-shared",
|
||||||
|
"-o", libPath,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
block, _ := pem.Decode([]byte(debugCert))
|
||||||
|
if block == nil {
|
||||||
|
return errors.New("no debug cert")
|
||||||
|
}
|
||||||
|
privKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if buildO == "" {
|
||||||
|
buildO = filepath.Base(pkg.Dir) + ".apk"
|
||||||
|
}
|
||||||
|
if !strings.HasSuffix(buildO, ".apk") {
|
||||||
|
return fmt.Errorf("output file name %q does not end in '.apk'", buildO)
|
||||||
|
}
|
||||||
|
var out io.Writer
|
||||||
|
if !buildN {
|
||||||
|
f, err := os.Create(buildO)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if cerr := f.Close(); err == nil {
|
||||||
|
err = cerr
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
out = f
|
||||||
|
}
|
||||||
|
|
||||||
|
var apkw *Writer
|
||||||
|
if !buildN {
|
||||||
|
apkw = NewWriter(out, privKey)
|
||||||
|
}
|
||||||
|
apkwcreate := func(name string) (io.Writer, error) {
|
||||||
|
if buildV {
|
||||||
|
fmt.Fprintf(os.Stderr, "apk: %s\n", name)
|
||||||
|
}
|
||||||
|
if buildN {
|
||||||
|
return ioutil.Discard, nil
|
||||||
|
}
|
||||||
|
return apkw.Create(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
w, err := apkwcreate("AndroidManifest.xml")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := w.Write(manifestData); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
w, err = apkwcreate("lib/armeabi/lib" + libName + ".so")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !buildN {
|
||||||
|
r, err := os.Open(libPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := io.Copy(w, r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if pkgImportsAL(pkg) {
|
||||||
|
alDir := filepath.Join(ndkccpath, "openal/lib")
|
||||||
|
filepath.Walk(alDir, func(path string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if info.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
name := "lib/" + path[len(alDir)+1:]
|
||||||
|
w, err := apkwcreate(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !buildN {
|
||||||
|
f, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
_, err = io.Copy(w, f)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add any assets.
|
||||||
|
assetsDir := filepath.Join(pkg.Dir, "assets")
|
||||||
|
assetsDirExists := true
|
||||||
|
fi, err := os.Stat(assetsDir)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
assetsDirExists = false
|
||||||
|
} else {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
assetsDirExists = fi.IsDir()
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
name := "assets/" + path[len(assetsDir)+1:]
|
||||||
|
w, err := apkwcreate(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
f, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
_, err = io.Copy(w, f)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("asset %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: add gdbserver to apk?
|
||||||
|
|
||||||
|
if !buildN {
|
||||||
|
if err := apkw.Close(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var importsALPkg = make(map[string]struct{})
|
||||||
|
|
||||||
|
// pkgImportsAL returns true if the given package or one of its
|
||||||
|
// dependencies imports the x/mobile/exp/audio/al package.
|
||||||
|
func pkgImportsAL(pkg *build.Package) bool {
|
||||||
|
for _, path := range pkg.Imports {
|
||||||
|
if path == "C" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, ok := importsALPkg[path]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
importsALPkg[path] = struct{}{}
|
||||||
|
if strings.HasPrefix(path, "golang.org/x/mobile/exp/audio/al") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
dPkg, err := ctx.Import(path, "", build.ImportComment)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "error reading OpenAL library: %v", err)
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
if pkgImportsAL(dPkg) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// A random uninteresting private key.
|
||||||
|
// Must be consistent across builds so newer app versions can be installed.
|
||||||
|
const debugCert = `
|
||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEowIBAAKCAQEAy6ItnWZJ8DpX9R5FdWbS9Kr1U8Z7mKgqNByGU7No99JUnmyu
|
||||||
|
NQ6Uy6Nj0Gz3o3c0BXESECblOC13WdzjsH1Pi7/L9QV8jXOXX8cvkG5SJAyj6hcO
|
||||||
|
LOapjDiN89NXjXtyv206JWYvRtpexyVrmHJgRAw3fiFI+m4g4Qop1CxcIF/EgYh7
|
||||||
|
rYrqh4wbCM1OGaCleQWaOCXxZGm+J5YNKQcWpjZRrDrb35IZmlT0bK46CXUKvCqK
|
||||||
|
x7YXHgfhC8ZsXCtsScKJVHs7gEsNxz7A0XoibFw6DoxtjKzUCktnT0w3wxdY7OTj
|
||||||
|
9AR8mobFlM9W3yirX8TtwekWhDNTYEu8dwwykwIDAQABAoIBAA2hjpIhvcNR9H9Z
|
||||||
|
BmdEecydAQ0ZlT5zy1dvrWI++UDVmIp+Ve8BSd6T0mOqV61elmHi3sWsBN4M1Rdz
|
||||||
|
3N38lW2SajG9q0fAvBpSOBHgAKmfGv3Ziz5gNmtHgeEXfZ3f7J95zVGhlHqWtY95
|
||||||
|
JsmuplkHxFMyITN6WcMWrhQg4A3enKLhJLlaGLJf9PeBrvVxHR1/txrfENd2iJBH
|
||||||
|
FmxVGILL09fIIktJvoScbzVOneeWXj5vJGzWVhB17DHBbANGvVPdD5f+k/s5aooh
|
||||||
|
hWAy/yLKocr294C4J+gkO5h2zjjjSGcmVHfrhlXQoEPX+iW1TGoF8BMtl4Llc+jw
|
||||||
|
lKWKfpECgYEA9C428Z6CvAn+KJ2yhbAtuRo41kkOVoiQPtlPeRYs91Pq4+NBlfKO
|
||||||
|
2nWLkyavVrLx4YQeCeaEU2Xoieo9msfLZGTVxgRlztylOUR+zz2FzDBYGicuUD3s
|
||||||
|
EqC0Wv7tiX6dumpWyOcVVLmR9aKlOUzA9xemzIsWUwL3PpyONhKSq7kCgYEA1X2F
|
||||||
|
f2jKjoOVzglhtuX4/SP9GxS4gRf9rOQ1Q8DzZhyH2LZ6Dnb1uEQvGhiqJTU8CXxb
|
||||||
|
7odI0fgyNXq425Nlxc1Tu0G38TtJhwrx7HWHuFcbI/QpRtDYLWil8Zr7Q3BT9rdh
|
||||||
|
moo4m937hLMvqOG9pyIbyjOEPK2WBCtKW5yabqsCgYEAu9DkUBr1Qf+Jr+IEU9I8
|
||||||
|
iRkDSMeusJ6gHMd32pJVCfRRQvIlG1oTyTMKpafmzBAd/rFpjYHynFdRcutqcShm
|
||||||
|
aJUq3QG68U9EAvWNeIhA5tr0mUEz3WKTt4xGzYsyWES8u4tZr3QXMzD9dOuinJ1N
|
||||||
|
+4EEumXtSPKKDG3M8Qh+KnkCgYBUEVSTYmF5EynXc2xOCGsuy5AsrNEmzJqxDUBI
|
||||||
|
SN/P0uZPmTOhJIkIIZlmrlW5xye4GIde+1jajeC/nG7U0EsgRAV31J4pWQ5QJigz
|
||||||
|
0+g419wxIUFryGuIHhBSfpP472+w1G+T2mAGSLh1fdYDq7jx6oWE7xpghn5vb9id
|
||||||
|
EKLjdwKBgBtz9mzbzutIfAW0Y8F23T60nKvQ0gibE92rnUbjPnw8HjL3AZLU05N+
|
||||||
|
cSL5bhq0N5XHK77sscxW9vXjG0LJMXmFZPp9F6aV6ejkMIXyJ/Yz/EqeaJFwilTq
|
||||||
|
Mc6xR47qkdzu0dQ1aPm4XD7AWDtIvPo/GG2DKOucLBbQc2cOWtKS
|
||||||
|
-----END RSA PRIVATE KEY-----
|
||||||
|
`
|
@ -7,38 +7,26 @@ package main
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"go/build"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func goIOSBuild(src string) error {
|
func goIOSBuild(pkg *build.Package) error {
|
||||||
|
src := pkg.ImportPath
|
||||||
if buildO != "" && !strings.HasSuffix(buildO, ".app") {
|
if buildO != "" && !strings.HasSuffix(buildO, ".app") {
|
||||||
return fmt.Errorf("-o must have an .app for target=ios")
|
return fmt.Errorf("-o must have an .app for target=ios")
|
||||||
}
|
}
|
||||||
|
|
||||||
dir := "$XCODEPROJ"
|
|
||||||
if !buildN {
|
|
||||||
tmp, err := ioutil.TempDir("", "xcodeproject")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
dir = tmp
|
|
||||||
defer os.RemoveAll(dir)
|
|
||||||
}
|
|
||||||
if buildX {
|
|
||||||
printcmd("mkdir %s", dir)
|
|
||||||
}
|
|
||||||
|
|
||||||
layout := map[string][]byte{
|
layout := map[string][]byte{
|
||||||
dir + "/main.xcodeproj/project.pbxproj": []byte(projPbxproj),
|
tmpdir + "/main.xcodeproj/project.pbxproj": []byte(projPbxproj),
|
||||||
dir + "/main/Info.plist": []byte(infoPlist),
|
tmpdir + "/main/Info.plist": []byte(infoPlist),
|
||||||
dir + "/main/Images.xcassets/AppIcon.appiconset/Contents.json": []byte(contentsJSON),
|
tmpdir + "/main/Images.xcassets/AppIcon.appiconset/Contents.json": []byte(contentsJSON),
|
||||||
}
|
}
|
||||||
|
|
||||||
for dst, v := range layout {
|
for dst, v := range layout {
|
||||||
@ -55,20 +43,13 @@ func goIOSBuild(src string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
armPath := filepath.Join(dir, "arm")
|
armPath := filepath.Join(tmpdir, "arm")
|
||||||
if err := goBuild(src, armPath, []string{
|
if err := goBuild(src, darwinArmEnv, "-o="+armPath); err != nil {
|
||||||
`GOOS=darwin`,
|
|
||||||
`GOARCH=arm`,
|
|
||||||
`GOARM=7`,
|
|
||||||
}); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
arm64Path := filepath.Join(dir, "arm64")
|
arm64Path := filepath.Join(tmpdir, "arm64")
|
||||||
if err := goBuild(src, arm64Path, []string{
|
if err := goBuild(src, darwinArm64Env, "-o="+arm64Path); err != nil {
|
||||||
`GOOS=darwin`,
|
|
||||||
`GOARCH=arm64`,
|
|
||||||
}); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,13 +58,13 @@ func goIOSBuild(src string) error {
|
|||||||
// TODO(jbd): Investigate the new announcements about iO9's fat binary
|
// TODO(jbd): Investigate the new announcements about iO9's fat binary
|
||||||
// size limitations are breaking this feature.
|
// size limitations are breaking this feature.
|
||||||
if buildX {
|
if buildX {
|
||||||
printcmd("xcrun lipo -create %s %s -o %s", armPath, arm64Path, filepath.Join(dir, "main/main"))
|
printcmd("xcrun lipo -create %s %s -o %s", armPath, arm64Path, filepath.Join(tmpdir, "main/main"))
|
||||||
}
|
}
|
||||||
if !buildN {
|
if !buildN {
|
||||||
cmd := exec.Command(
|
cmd := exec.Command(
|
||||||
"xcrun", "lipo",
|
"xcrun", "lipo",
|
||||||
"-create", armPath, arm64Path,
|
"-create", armPath, arm64Path,
|
||||||
"-o", filepath.Join(dir, "main/main"),
|
"-o", filepath.Join(tmpdir, "main/main"),
|
||||||
)
|
)
|
||||||
cmd.Stderr = os.Stderr
|
cmd.Stderr = os.Stderr
|
||||||
if err := cmd.Run(); err != nil {
|
if err := cmd.Run(); err != nil {
|
||||||
@ -92,7 +73,7 @@ func goIOSBuild(src string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO(jbd): Set the launcher icon.
|
// TODO(jbd): Set the launcher icon.
|
||||||
if err := iosCopyAssets(dir); err != nil {
|
if err := iosCopyAssets(pkg, tmpdir); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,7 +81,7 @@ func goIOSBuild(src string) error {
|
|||||||
cmd := exec.Command(
|
cmd := exec.Command(
|
||||||
"xcrun", "xcodebuild",
|
"xcrun", "xcodebuild",
|
||||||
"-configuration", "Release",
|
"-configuration", "Release",
|
||||||
"-project", dir+"/main.xcodeproj",
|
"-project", tmpdir+"/main.xcodeproj",
|
||||||
)
|
)
|
||||||
|
|
||||||
if buildX {
|
if buildX {
|
||||||
@ -128,21 +109,21 @@ func goIOSBuild(src string) error {
|
|||||||
buildO = path.Base(pkg.ImportPath) + ".app"
|
buildO = path.Base(pkg.ImportPath) + ".app"
|
||||||
}
|
}
|
||||||
if buildX {
|
if buildX {
|
||||||
printcmd("mv %s %s", dir+"/build/Release-iphoneos/main.app", buildO)
|
printcmd("mv %s %s", tmpdir+"/build/Release-iphoneos/main.app", buildO)
|
||||||
}
|
}
|
||||||
if !buildN {
|
if !buildN {
|
||||||
// if output already exists, remove.
|
// if output already exists, remove.
|
||||||
if err := os.RemoveAll(buildO); err != nil {
|
if err := os.RemoveAll(buildO); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := os.Rename(dir+"/build/Release-iphoneos/main.app", buildO); err != nil {
|
if err := os.Rename(tmpdir+"/build/Release-iphoneos/main.app", buildO); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func iosCopyAssets(xcodeProjDir string) error {
|
func iosCopyAssets(pkg *build.Package, xcodeProjDir string) error {
|
||||||
dstAssets := xcodeProjDir + "/main/assets"
|
dstAssets := xcodeProjDir + "/main/assets"
|
||||||
if buildX {
|
if buildX {
|
||||||
printcmd("mkdir -p %s", dstAssets)
|
printcmd("mkdir -p %s", dstAssets)
|
||||||
@ -199,45 +180,6 @@ func iosCopyAssets(xcodeProjDir string) error {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func goBuild(src, o string, env []string) error {
|
|
||||||
goroot := goEnv("GOROOT")
|
|
||||||
gopath := goEnv("GOPATH")
|
|
||||||
cmd := exec.Command(
|
|
||||||
`go`,
|
|
||||||
`build`,
|
|
||||||
`-tags=`+strconv.Quote(strings.Join(ctx.BuildTags, ",")),
|
|
||||||
)
|
|
||||||
if buildV {
|
|
||||||
cmd.Args = append(cmd.Args, "-v")
|
|
||||||
}
|
|
||||||
if buildI {
|
|
||||||
cmd.Args = append(cmd.Args, "-i")
|
|
||||||
}
|
|
||||||
if buildX {
|
|
||||||
cmd.Args = append(cmd.Args, "-x")
|
|
||||||
}
|
|
||||||
cmd.Args = append(cmd.Args, "-o="+o)
|
|
||||||
cmd.Args = append(cmd.Args, src)
|
|
||||||
// TODO(jbd): Remove clangwrap.sh dependency by implementing clangwrap.sh
|
|
||||||
// in Go in this package.
|
|
||||||
cmd.Env = append(env, []string{
|
|
||||||
`CGO_ENABLED=1`,
|
|
||||||
`GOROOT=` + goroot,
|
|
||||||
`GOPATH=` + gopath,
|
|
||||||
`CC=` + filepath.Join(goroot, "misc/ios/clangwrap.sh"),
|
|
||||||
`CCX=` + filepath.Join(goroot, "misc/ios/clangwrap.sh"),
|
|
||||||
}...)
|
|
||||||
cmd.Stderr = os.Stderr
|
|
||||||
if buildX {
|
|
||||||
printcmd("%s", strings.Join(cmd.Env, " ")+" "+strings.Join(cmd.Args, " "))
|
|
||||||
}
|
|
||||||
if !buildN {
|
|
||||||
cmd.Env = environ(cmd.Env)
|
|
||||||
return cmd.Run()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
const infoPlist = `<?xml version="1.0" encoding="UTF-8"?>
|
const infoPlist = `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
@ -46,8 +46,8 @@ func TestBuild(t *testing.T) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var buildTmpl = template.Must(template.New("output").Parse(`WORK=$WORK
|
var buildTmpl = template.Must(template.New("output").Parse(`GOMOBILE={{.GOPATH}}/pkg/gomobile
|
||||||
GOMOBILE={{.GOPATH}}/pkg/gomobile
|
WORK=$WORK
|
||||||
GOOS=android GOARCH=arm GOARM=7 CGO_ENABLED=1 CC=$GOMOBILE/android-{{.NDK}}/arm/bin/arm-linux-androideabi-gcc{{.EXE}} CXX=$GOMOBILE/android-{{.NDK}}/arm/bin/arm-linux-androideabi-g++{{.EXE}} GOGCCFLAGS="-fPIC -marm -pthread -fmessage-length=0" GOROOT=$GOROOT GOPATH=$GOPATH go build -tags="" -x -buildmode=c-shared -o $WORK/libbasic.so golang.org/x/mobile/example/basic
|
GOOS=android GOARCH=arm GOARM=7 CC=$GOMOBILE/android-{{.NDK}}/arm/bin/arm-linux-androideabi-gcc{{.EXE}} CXX=$GOMOBILE/android-{{.NDK}}/arm/bin/arm-linux-androideabi-g++{{.EXE}} GOGCCFLAGS="-fPIC -marm -pthread -fmessage-length=0" CGO_ENABLED=1 GOROOT=$GOROOT GOPATH=$GOPATH go build -tags="" -x -buildmode=c-shared -o $WORK/libbasic.so golang.org/x/mobile/example/basic
|
||||||
rm -r -f "$WORK"
|
rm -r -f "$WORK"
|
||||||
`))
|
`))
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
// DO NOT EDIT. GENERATED BY 'gomobile help documentation'.
|
// DO NOT EDIT. GENERATED BY 'gomobile help documentation doc.go'.
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Gomobile is a tool for building and running mobile apps written in Go.
|
Gomobile is a tool for building and running mobile apps written in Go.
|
||||||
|
105
cmd/gomobile/env.go
Normal file
105
cmd/gomobile/env.go
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// General mobile build environment. Initialized by envInit.
|
||||||
|
var (
|
||||||
|
cwd string
|
||||||
|
gomobilepath string // $GOPATH/pkg/gomobile
|
||||||
|
ndkccpath string // $GOPATH/pkg/gomobile/android-{{.NDK}}
|
||||||
|
|
||||||
|
darwinArmEnv []string
|
||||||
|
darwinArm64Env []string
|
||||||
|
androidArmEnv []string
|
||||||
|
)
|
||||||
|
|
||||||
|
func envInit() (cleanup func(), err error) {
|
||||||
|
cwd, err = os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find gomobilepath.
|
||||||
|
gopath := goEnv("GOPATH")
|
||||||
|
for _, p := range filepath.SplitList(gopath) {
|
||||||
|
gomobilepath = filepath.Join(p, "pkg", "gomobile")
|
||||||
|
if _, err := os.Stat(gomobilepath); err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if buildX {
|
||||||
|
fmt.Fprintln(xout, "GOMOBILE="+gomobilepath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the toolchain is in a good state.
|
||||||
|
version, err := goVersion()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if gomobilepath == "" {
|
||||||
|
return nil, errors.New("toolchain not installed, run `gomobile init`")
|
||||||
|
}
|
||||||
|
verpath := filepath.Join(gomobilepath, "version")
|
||||||
|
installedVersion, err := ioutil.ReadFile(verpath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("toolchain partially installed, run `gomobile init`")
|
||||||
|
}
|
||||||
|
if !bytes.Equal(installedVersion, version) {
|
||||||
|
return nil, errors.New("toolchain out of date, run `gomobile init`")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup the cross-compiler environments.
|
||||||
|
|
||||||
|
// TODO(crawshaw): Remove ndkccpath global.
|
||||||
|
ndkccpath = filepath.Join(gomobilepath, "android-"+ndkVersion)
|
||||||
|
ndkccbin := filepath.Join(ndkccpath, "arm", "bin")
|
||||||
|
|
||||||
|
androidEnv := []string{
|
||||||
|
"CC=" + filepath.Join(ndkccbin, "arm-linux-androideabi-gcc"),
|
||||||
|
"CXX=" + filepath.Join(ndkccbin, "arm-linux-androideabi-g++"),
|
||||||
|
`GOGCCFLAGS="-fPIC -marm -pthread -fmessage-length=0"`,
|
||||||
|
}
|
||||||
|
androidArmEnv = append([]string{
|
||||||
|
"GOOS=android",
|
||||||
|
"GOARCH=arm",
|
||||||
|
"GOARM=7",
|
||||||
|
}, androidEnv...)
|
||||||
|
|
||||||
|
// TODO(jbd): Remove clangwrap.sh dependency by implementing clangwrap.sh
|
||||||
|
// in Go in this package.
|
||||||
|
goroot := goEnv("GOROOT")
|
||||||
|
iosEnv := []string{
|
||||||
|
"CC=" + filepath.Join(goroot, "misc/ios/clangwrap.sh"),
|
||||||
|
"CCX=" + filepath.Join(goroot, "misc/ios/clangwrap.sh"),
|
||||||
|
}
|
||||||
|
darwinArmEnv = append([]string{
|
||||||
|
"GOOS=darwin",
|
||||||
|
"GOARCH=arm",
|
||||||
|
}, iosEnv...)
|
||||||
|
darwinArm64Env = append([]string{
|
||||||
|
"GOOS=darwin",
|
||||||
|
"GOARCH=arm64",
|
||||||
|
}, iosEnv...)
|
||||||
|
|
||||||
|
// We need a temporary directory when assembling an apk/app.
|
||||||
|
if buildN {
|
||||||
|
tmpdir = "$WORK"
|
||||||
|
} else {
|
||||||
|
tmpdir, err = ioutil.TempDir("", "gomobile-work-")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if buildX {
|
||||||
|
fmt.Fprintln(xout, "WORK="+tmpdir)
|
||||||
|
}
|
||||||
|
|
||||||
|
return func() { removeAll(tmpdir) }, nil
|
||||||
|
}
|
@ -4,7 +4,6 @@
|
|||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
// TODO(crawshaw): build darwin/arm cross compiler on darwin/{386,amd64}
|
|
||||||
// TODO(crawshaw): android/{386,arm64}
|
// TODO(crawshaw): android/{386,arm64}
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
Loading…
x
Reference in New Issue
Block a user