[mobile] cmd/gomobile: bind command for running gobind and building the libraries.

280 views
Skip to first unread message

Hyang-Ah Hana Kim (Gerrit)

unread,
Feb 13, 2015, 1:07:52 PM2/13/15
to David Crawshaw, Ian Lance Taylor, Hyang-Ah Hana Kim, golang-co...@googlegroups.com
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>

David Crawshaw (Gerrit)

unread,
Feb 13, 2015, 2:00:28 PM2/13/15
to Hyang-Ah Hana Kim, golang-co...@googlegroups.com
David Crawshaw has posted comments on this change.

cmd/gomobile: bind command for running gobind and building the libraries.

Patch Set 1: Code-Review+2

(5 comments)

https://go-review.googlesource.com/#/c/4833/1/cmd/gomobile/bind.go
File cmd/gomobile/bind.go:

Line 52: The -output flag specifies the output directory. If not specified,
-outdir


Line 53: the current directory is used.
I'm not sure about the semantics of this. We are in an unfortunate place
right now with our examples where the make.bash script puts binaries into
the user's GOPATH. I'd rather avoid this if possible, and I'm afraid that
defaulting to PWD might encourage people to accidentally plaster files over
their GOPATH.

How about, at least for starters, requiring users to specify an -outdir?


Line 152: if err :=
binder.GenJava(pathJoin(androidOutdir, "src/main/java/go/"+binder.pkg.Name()));
err != nil {
Add a TODO here about using a package name derived from the gopackage. I'll
take a look at it when I deal with the similar package name in the
AndroidManifest.xml.


Line 220: err := mkdir(filepath.Dir(filename))
mkdir is a no-op if buildN, so move this before the if statement so we can
see it with -x -n.


https://go-review.googlesource.com/#/c/4833/1/cmd/gomobile/main.go
File cmd/gomobile/main.go:

Line 97: cmdBind,
One alphabetical group please.


--
https://go-review.googlesource.com/4833
Gerrit-Reviewer: David Crawshaw <craw...@golang.org>
Gerrit-HasComments: Yes

Hyang-Ah Hana Kim (Gerrit)

unread,
Feb 13, 2015, 2:44:20 PM2/13/15
to Hyang-Ah Hana Kim, David Crawshaw, golang-co...@googlegroups.com
Reviewers: David Crawshaw

Hyang-Ah Hana Kim uploaded a new patch set:
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, 369 insertions(+), 51 deletions(-)

Hyang-Ah Hana Kim (Gerrit)

unread,
Feb 13, 2015, 2:45:37 PM2/13/15
to Hyang-Ah Hana Kim, David Crawshaw, golang-co...@googlegroups.com
Reviewers: David Crawshaw

Hyang-Ah Hana Kim uploaded a new patch set:
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, 369 insertions(+), 51 deletions(-)

Hyang-Ah Hana Kim (Gerrit)

unread,
Feb 13, 2015, 2:47:53 PM2/13/15
to Hyang-Ah Hana Kim, David Crawshaw, golang-co...@googlegroups.com
Hyang-Ah Hana Kim has posted comments on this change.

cmd/gomobile: bind command for running gobind and building the libraries.

Patch Set 1:
Line 52: The -output flag specifies the output directory. If not specified,
> -outdir
Done


Line 53: the current directory is used.
> I'm not sure about the semantics of this. We are in an unfortunate place
> ri
Done. required.


Line 152: if err :=
binder.GenJava(pathJoin(androidOutdir, "src/main/java/go/"+binder.pkg.Name()));
err != nil {
> Add a TODO here about using a package name derived from the gopackage.
> I'll
Done


Line 220: err := mkdir(filepath.Dir(filename))
> mkdir is a no-op if buildN, so move this before the if statement so we can
Done


https://go-review.googlesource.com/#/c/4833/1/cmd/gomobile/main.go
File cmd/gomobile/main.go:

Line 97: cmdBind,
> One alphabetical group please.
Done


--
https://go-review.googlesource.com/4833
Gerrit-Reviewer: David Crawshaw <craw...@golang.org>
Gerrit-Reviewer: Hyang-Ah Hana Kim <hya...@gmail.com>
Gerrit-HasComments: Yes

Hyang-Ah Hana Kim (Gerrit)

unread,
Feb 13, 2015, 2:51:20 PM2/13/15
to Hyang-Ah Hana Kim, golang-...@googlegroups.com, David Crawshaw, golang-co...@googlegroups.com
Hyang-Ah Hana Kim has submitted this change and it was merged.

cmd/gomobile: bind command for running gobind and building the libraries.

Change-Id: Ic4bf580b9c3f9757611ec7644eaf998d60f0fafc
Reviewed-on: https://go-review.googlesource.com/4833
Reviewed-by: David Crawshaw <craw...@golang.org>
---
A cmd/gomobile/bind.go
M cmd/gomobile/build.go
M cmd/gomobile/main.go
3 files changed, 369 insertions(+), 51 deletions(-)

Approvals:
David Crawshaw: Looks good to me, approved


--
https://go-review.googlesource.com/4833
Gerrit-Reviewer: David Crawshaw <craw...@golang.org>
Reply all
Reply to author
Forward
0 new messages