Reviewers: David Crawshaw
Hyang-Ah Hana Kim uploaded a change:
https://go-review.googlesource.com/4833
cmd/gomobile: bind command for running gobind and building the libraries.
Change-Id: Ic4bf580b9c3f9757611ec7644eaf998d60f0fafc
---
A cmd/gomobile/bind.go
M cmd/gomobile/build.go
M cmd/gomobile/main.go
3 files changed, 368 insertions(+), 51 deletions(-)
diff --git a/cmd/gomobile/bind.go b/cmd/gomobile/bind.go
new file mode 100644
index 0000000..bdf5040
--- /dev/null
+++ b/cmd/gomobile/bind.go
@@ -0,0 +1,305 @@
+// 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 (
+ "errors"
+ "fmt"
+ "go/ast"
+ "go/build"
+ "go/parser"
+ "go/scanner"
+ "go/token"
+ "io"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "text/template"
+ "unicode"
+ "unicode/utf8"
+
+ "
golang.org/x/mobile/bind"
+ "
golang.org/x/tools/go/loader"
+ "
golang.org/x/tools/go/types"
+)
+
+// ctx, pkg, ndkccpath, tmpdir in build.go
+
+var cmdBind = &command{
+ run: runBind,
+ Name: "bind",
+ Usage: "[package]",
+ Short: "build a shared library for android APK and/or iOS app",
+ Long: `
+Bind generates language bindings like gobind
(
golang.org/x/mobile/cmd/gobind)
+for a package and builds a shared library for each platform from the go
binding
+code.
+
+For Android, the bind command will place the generated Java API stubs and
the
+compiled shared libraries in the android subdirectory of the following
layout.
+
+<outdir>/android
+ libs/
+ armeabi-v7/libgojni.so
+ ...
+ src/main/java/go/
+ Seq.java
+ Go.java
+ mypackage/Mypackage.java
+
+The -output flag specifies the output directory. If not specified,
+the current directory is used.
+
+The -v flag provides verbose output, including the list of packages built.
+
+These build flags are shared by the build command.
+For documentation, see 'go help build':
+ -a
+ -i
+ -n
+ -x
+ -tags 'tag list'
+`,
+}
+
+// TODO: -mobile
+
+var bindOutdir *string // -outdir
+func init() {
+ bindOutdir = cmdBind.flag.String("outdir", "", "output directory. Default
is the current directory.")
+}
+
+func runBind(cmd *command) error {
+ cwd, err := os.Getwd()
+ if err != nil {
+ panic(err)
+ }
+ args := cmd.flag.Args()
+
+ var bindPkg *build.Package
+ switch len(args) {
+ case 0:
+ bindPkg, err = ctx.ImportDir(cwd, build.ImportComment)
+ case 1:
+ bindPkg, err = ctx.Import(args[0], cwd, build.ImportComment)
+ default:
+ cmd.usage()
+ os.Exit(1)
+ }
+ if err != nil {
+ return err
+ }
+
+ if *bindOutdir == "" {
+ *bindOutdir = cwd
+ }
+ if !buildN {
+ sentinel := filepath.Join(*bindOutdir, "gomobile-bind-sentinel")
+ if err := ioutil.WriteFile(sentinel, []byte("write test"), 0644); err !=
nil {
+ return fmt.Errorf("output directory %q not writable", *bindOutdir)
+ }
+ os.Remove(sentinel)
+ }
+
+ 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
+ }
+
+ pathJoin := func(a, b string) string {
+ return filepath.Join(a, filepath.FromSlash(b))
+ }
+
+ mainFile := pathJoin(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)
+ }
+
+ androidOutdir := pathJoin(*bindOutdir, "android")
+
+ err = gobuild(mainFile,
pathJoin(androidOutdir, "libs/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.
+
+ if err :=
binder.GenJava(pathJoin(androidOutdir, "src/main/java/go/"+
binder.pkg.Name()));
err != nil {
+ return err
+ }
+
+ src := pathJoin(repo, "app/Go.java")
+ dst := pathJoin(androidOutdir, "src/main/java/go/Go.java")
+ if err := symlink(src, dst); err != nil {
+ return err
+ }
+
+ src = pathJoin(repo, "bind/java/Seq.java")
+ dst = pathJoin(androidOutdir, "src/main/java/go/Seq.java")
+ if err := symlink(src, dst); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+type binder struct {
+ files []*ast.File
+ fset *token.FileSet
+ pkg *types.Package
+}
+
+func (b *binder) GenJava(outdir string) error {
+ firstRune, size := utf8.DecodeRuneInString(
b.pkg.Name())
+ className := string(unicode.ToUpper(firstRune)) +
b.pkg.Name()[size:]
+ javaFile := filepath.Join(outdir, className+".java")
+
+ if buildX {
+ printcmd("gobind -lang=java %s > %s", b.pkg.Path(), javaFile)
+ }
+
+ generate := func(w io.Writer) error {
+ return bind.GenJava(w, b.fset, b.pkg)
+ }
+ if err := writeFile(javaFile, generate); err != nil {
+ return err
+ }
+ return nil
+}
+
+func (b *binder) GenGo(outdir string) error {
+ pkgName := "go_" +
b.pkg.Name()
+ goFile := filepath.Join(outdir, pkgName, pkgName+".go")
+
+ if buildX {
+ printcmd("gobind -lang=go %s > %s", b.pkg.Path(), goFile)
+ }
+
+ generate := func(w io.Writer) error {
+ return bind.GenGo(w, b.fset, b.pkg)
+ }
+ if err := writeFile(goFile, generate); err != nil {
+ return err
+ }
+ return nil
+}
+
+func writeFile(filename string, generate func(io.Writer) error) error {
+ if buildV {
+ fmt.Fprintf(os.Stderr, "write %s\n", filename)
+ }
+ if buildN {
+ return generate(ioutil.Discard)
+ }
+
+ err := mkdir(filepath.Dir(filename))
+ if err != nil {
+ return err
+ }
+
+ f, err := os.Create(filename)
+ if err != nil {
+ return err
+ }
+ defer func() {
+ if cerr := f.Close(); err == nil {
+ err = cerr
+ }
+ }()
+
+ return generate(f)
+}
+
+func newBinder(bindPkg *build.Package) (*binder, error) {
+ if bindPkg.Name == "main" {
+ return nil, fmt.Errorf("package %q: can only bind a library package",
bindPkg.Name)
+ }
+
+ if len(bindPkg.CgoFiles) > 0 {
+ return nil, fmt.Errorf("cannot use cgo-dependent package as service
definition: %s", bindPkg.CgoFiles[0])
+ }
+
+ fset := token.NewFileSet()
+
+ hasErr := false
+ var files []*ast.File
+ for _, filename := range bindPkg.GoFiles {
+ p := filepath.Join(bindPkg.Dir, filename)
+ file, err := parser.ParseFile(fset, p, nil, parser.AllErrors)
+ if err != nil {
+ hasErr = true
+ if list, _ := err.(scanner.ErrorList); len(list) > 0 {
+ for _, err := range list {
+ fmt.Fprintln(os.Stderr, err)
+ }
+ } else {
+ fmt.Fprintln(os.Stderr, err)
+ }
+ }
+ files = append(files, file)
+ }
+
+ if hasErr {
+ return nil, errors.New("package parsing failed.")
+ }
+
+ conf := loader.Config{
+ SourceImports: true,
+ Fset: fset,
+ }
+ conf.TypeChecker.Error = func(err error) {
+ fmt.Fprintln(os.Stderr, err)
+ }
+
+ conf.CreateFromFiles(bindPkg.ImportPath, files...)
+ program, err := conf.Load()
+ if err != nil {
+ return nil, err
+ }
+ b := &binder{
+ files: files,
+ fset: fset,
+ pkg: program.Created[0].Pkg,
+ }
+ return b, nil
+}
+
+var androidMainTmpl = template.Must(template.New("android.go").Parse(`
+package main
+
+import (
+ "
golang.org/x/mobile/app"
+
+ _ "
golang.org/x/mobile/bind/java"
+ _ "{{.}}"
+)
+
+func main() {
+ app.Run(app.Callbacks{})
+}
+`))
diff --git a/cmd/gomobile/build.go b/cmd/gomobile/build.go
index b78491c..ecdd18f 100644
--- a/cmd/gomobile/build.go
+++ b/cmd/gomobile/build.go
@@ -137,58 +137,9 @@
}
libPath := filepath.Join(tmpdir, "lib"+libName+".so")
- gopath := goEnv("GOPATH")
- for _, p := range filepath.SplitList(gopath) {
- ndkccpath = filepath.Join(p,
filepath.FromSlash("pkg/gomobile/android-"+ndkVersion))
- if _, err = os.Stat(filepath.Join(ndkccpath, "arm", "bin")); err == nil {
- break
- }
+ if err := gobuild(pkg.ImportPath, libPath); err != nil {
+ return err
}
- if err != nil || ndkccpath == "" {
- // TODO(crawshaw): call gomobile init
- return fmt.Errorf("android toolchain not installed in
$GOPATH/pkg/gomobile, run:\n\tgomobile init")
- }
- if buildX {
- fmt.Fprintln(os.Stderr, "NDKCCPATH="+ndkccpath)
- }
-
- gocmd := exec.Command(
- `go`,
- `build`,
- `-ldflags="-shared"`,
- `-tags=`+strconv.Quote(strings.Join(ctx.BuildTags, ",")),
- `-o`, libPath)
- if buildV {
- gocmd.Args = append(gocmd.Args, "-v")
- }
- if buildI {
- gocmd.Args = append(gocmd.Args, "-i")
- }
-
- gocmd.Args = append(gocmd.Args, pkg.ImportPath)
-
- gocmd.Stdout = os.Stdout
- gocmd.Stderr = os.Stderr
- gocmd.Env = []string{
- `GOOS=android`,
- `GOARCH=arm`,
- `GOARM=7`,
- `CGO_ENABLED=1`,
- `CC=` +
filepath.Join(ndkccpath, "arm", "bin", "arm-linux-androideabi-gcc"),
- `CXX=` +
filepath.Join(ndkccpath, "arm", "bin", "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 {
- if err := gocmd.Run(); err != nil {
- return err
- }
- }
-
block, _ := pem.Decode([]byte(debugCert))
if block == nil {
return errors.New("no debug cert")
@@ -322,6 +273,62 @@
cmd.flag.BoolVar(&buildX, "x", false, "")
}
+func gobuild(src, libPath string) error {
+ var err error
+ gopath := goEnv("GOPATH")
+ for _, p := range filepath.SplitList(gopath) {
+ ndkccpath = filepath.Join(p,
filepath.FromSlash("pkg/gomobile/android-"+ndkVersion))
+ if _, err = os.Stat(filepath.Join(ndkccpath, "arm", "bin")); err == nil {
+ break
+ }
+ }
+ if err != nil || ndkccpath == "" {
+ // TODO(crawshaw): call gomobile init
+ return fmt.Errorf("android toolchain not installed in
$GOPATH/pkg/gomobile, run:\n\tgomobile init")
+ }
+ if buildX {
+ fmt.Fprintln(os.Stderr, "NDKCCPATH="+ndkccpath)
+ }
+
+ gocmd := exec.Command(
+ `go`,
+ `build`,
+ `-ldflags="-shared"`,
+ `-tags=`+strconv.Quote(strings.Join(ctx.BuildTags, ",")),
+ `-o`, libPath)
+ if buildV {
+ gocmd.Args = append(gocmd.Args, "-v")
+ }
+ if buildI {
+ gocmd.Args = append(gocmd.Args, "-i")
+ }
+
+ 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(ndkccpath, "arm", "bin", "arm-linux-androideabi-gcc"),
+ `CXX=` +
filepath.Join(ndkccpath, "arm", "bin", "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 {
+ if err := gocmd.Run(); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
func init() {
buildO = cmdBuild.flag.String("o", "", "output file")
addBuildFlags(cmdBuild)
@@ -331,6 +338,9 @@
addBuildFlagsNVX(cmdInstall)
addBuildFlagsNVX(cmdInit)
+
+ addBuildFlags(cmdBind)
+ addBuildFlagsNVX(cmdBind)
}
// A random uninteresting private key.
diff --git a/cmd/gomobile/main.go b/cmd/gomobile/main.go
index 1867c26..859235a 100644
--- a/cmd/gomobile/main.go
+++ b/cmd/gomobile/main.go
@@ -93,6 +93,8 @@
cmdBuild,
cmdInit,
cmdInstall,
+
+ cmdBind,
}
type command struct {
--
https://go-review.googlesource.com/4833
Gerrit-Reviewer: David Crawshaw <
craw...@golang.org>