bind: Android support for multiple packages.
As discussed in golang/go#12245
Usage: gomobile bind [options] a.b.c x.y.z
For java gobind and gomobile will generate go.c.C.java and go.z.Z.java.
If -javapkg=com.example is specified they will generate
com.example.C.java and com.example.Z.java.
Tested on Darwin.
Change-Id: Ia8e57c8fec7967131d55de71cc705d9e736ccca0
Reviewed-on: https://go-review.googlesource.com/17023
Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
diff --git a/cmd/gomobile/bind.go b/cmd/gomobile/bind.go
index 4254076..ba76fab 100644
--- a/cmd/gomobile/bind.go
+++ b/cmd/gomobile/bind.go
@@ -87,15 +87,13 @@
return fmt.Errorf("-prefix is supported only for ios target")
}
- var pkg *build.Package
+ var pkgs []*build.Package
switch len(args) {
case 0:
- pkg, err = ctx.ImportDir(cwd, build.ImportComment)
- case 1:
- pkg, err = ctx.Import(args[0], cwd, build.ImportComment)
+ pkgs = make([]*build.Package, 1)
+ pkgs[0], err = ctx.ImportDir(cwd, build.ImportComment)
default:
- cmd.usage()
- os.Exit(1)
+ pkgs, err = importPackages(args)
}
if err != nil {
return err
@@ -103,14 +101,28 @@
switch buildTarget {
case "android":
- return goAndroidBind(pkg)
+ return goAndroidBind(pkgs)
case "ios":
- return goIOSBind(pkg)
+ if len(pkgs) > 1 {
+ return fmt.Errorf("binding multiple packages not supported for ios")
+ }
+ return goIOSBind(pkgs)
default:
return fmt.Errorf(`unknown -target, %q.`, buildTarget)
}
}
+func importPackages(args []string) ([]*build.Package, error) {
+ pkgs := make([]*build.Package, len(args))
+ for i, path := range args {
+ var err error
+ if pkgs[i], err = ctx.Import(path, cwd, build.ImportComment); err != nil {
+ return nil, fmt.Errorf("package %q: %v", path, err)
+ }
+ }
+ return pkgs, nil
+}
+
var (
bindPrefix string // -prefix
bindJavaPkg string // -javapkg
@@ -127,7 +139,7 @@
type binder struct {
files []*ast.File
fset *token.FileSet
- pkg *types.Package
+ pkgs []*types.Package
}
func (b *binder) GenObjc(outdir string) error {
@@ -135,7 +147,7 @@
if bindPrefix == "" {
bindPrefix = bindPrefixDefault
}
- name := strings.Title(b.pkg.Name())
+ name := strings.Title(b.pkgs[0].Name())
bindOption := "-lang=objc"
if bindPrefix != bindPrefixDefault {
bindOption += " -prefix=" + bindPrefix
@@ -146,12 +158,12 @@
generate := func(w io.Writer) error {
if buildX {
- printcmd("gobind %s -outdir=%s %s", bindOption, outdir, b.pkg.Path())
+ printcmd("gobind %s -outdir=%s %s", bindOption, outdir, b.pkgs[0].Path())
}
if buildN {
return nil
}
- return bind.GenObjc(w, b.fset, b.pkg, bindPrefix, false)
+ return bind.GenObjc(w, b.fset, b.pkgs[0], bindPrefix, false)
}
if err := writeFile(mfile, generate); err != nil {
return err
@@ -160,7 +172,7 @@
if buildN {
return nil
}
- return bind.GenObjc(w, b.fset, b.pkg, bindPrefix, true)
+ return bind.GenObjc(w, b.fset, b.pkgs[0], bindPrefix, true)
}
if err := writeFile(hfile, generate); err != nil {
return err
@@ -173,8 +185,8 @@
return copyFile(filepath.Join(outdir, "seq.h"), filepath.Join(objcPkg.Dir, "seq.h"))
}
-func (b *binder) GenJava(outdir string) error {
- className := strings.Title(b.pkg.Name())
+func (b *binder) GenJava(pkg *types.Package, outdir string) error {
+ className := strings.Title(pkg.Name())
javaFile := filepath.Join(outdir, className+".java")
bindOption := "-lang=java"
if bindJavaPkg != "" {
@@ -183,12 +195,12 @@
generate := func(w io.Writer) error {
if buildX {
- printcmd("gobind %s -outdir=%s %s", bindOption, outdir, b.pkg.Path())
+ printcmd("gobind %s -outdir=%s %s", bindOption, outdir, pkg.Path())
}
if buildN {
return nil
}
- return bind.GenJava(w, b.fset, b.pkg, bindJavaPkg)
+ return bind.GenJava(w, b.fset, pkg, bindJavaPkg)
}
if err := writeFile(javaFile, generate); err != nil {
return err
@@ -196,19 +208,19 @@
return nil
}
-func (b *binder) GenGo(outdir string) error {
- pkgName := "go_" + b.pkg.Name()
+func (b *binder) GenGo(pkg *types.Package, outdir string) error {
+ pkgName := "go_" + pkg.Name()
outdir = filepath.Join(outdir, pkgName)
goFile := filepath.Join(outdir, pkgName+"main.go")
generate := func(w io.Writer) error {
if buildX {
- printcmd("gobind -lang=go -outdir=%s %s", outdir, b.pkg.Path())
+ printcmd("gobind -lang=go -outdir=%s %s", outdir, pkg.Path())
}
if buildN {
return nil
}
- return bind.GenGo(w, b.fset, b.pkg)
+ return bind.GenGo(w, b.fset, pkg)
}
if err := writeFile(goFile, generate); err != nil {
return err
@@ -264,11 +276,15 @@
return generate(f)
}
-func loadExportData(importPath string, env []string, args ...string) (*types.Package, error) {
+func loadExportData(pkgs []*build.Package, env []string, args ...string) ([]*types.Package, error) {
// Compile the package. This will produce good errors if the package
// doesn't typecheck for some reason, and is a necessary step to
// building the final output anyway.
- if err := goInstall(importPath, env, args...); err != nil {
+ paths := make([]string, len(pkgs))
+ for i, p := range pkgs {
+ paths[i] = p.ImportPath
+ }
+ if err := goInstall(paths, env, args...); err != nil {
return nil, err
}
@@ -284,33 +300,40 @@
if err := mkdir(filepath.Join(fakegopath, "pkg")); err != nil {
return nil, err
}
- src := filepath.Join(pkgdir(env), importPath+".a")
- dst := filepath.Join(fakegopath, "pkg/"+getenv(env, "GOOS")+"_"+getenv(env, "GOARCH")+"/"+importPath+".a")
- if err := copyFile(dst, src); err != nil {
- return nil, err
+ typePkgs := make([]*types.Package, len(pkgs))
+ for i, p := range pkgs {
+ importPath := p.ImportPath
+ src := filepath.Join(pkgdir(env), importPath+".a")
+ dst := filepath.Join(fakegopath, "pkg/"+getenv(env, "GOOS")+"_"+getenv(env, "GOARCH")+"/"+importPath+".a")
+ if err := copyFile(dst, src); err != nil {
+ return nil, err
+ }
+ if buildN {
+ typePkgs[i] = types.NewPackage(importPath, path.Base(importPath))
+ continue
+ }
+ oldDefault := build.Default
+ build.Default = ctx // copy
+ build.Default.GOPATH = fakegopath
+ p, err := importer.Default().Import(importPath)
+ build.Default = oldDefault
+ if err != nil {
+ return nil, err
+ }
+ typePkgs[i] = p
}
- if buildN {
- return types.NewPackage(importPath, path.Base(importPath)), nil
- }
- oldDefault := build.Default
- build.Default = ctx // copy
- build.Default.GOPATH = fakegopath
- p, err := importer.Default().Import(importPath)
- build.Default = oldDefault
- if err != nil {
- return nil, err
- }
-
- return p, nil
+ return typePkgs, nil
}
-func newBinder(p *types.Package) (*binder, error) {
- if p.Name() == "main" {
- return nil, fmt.Errorf("package %q: can only bind a library package", p.Name())
+func newBinder(pkgs []*types.Package) (*binder, error) {
+ for _, pkg := range pkgs {
+ if pkg.Name() == "main" {
+ return nil, fmt.Errorf("package %q (%q): can only bind a library package", pkg.Name(), pkg.Path())
+ }
}
b := &binder{
fset: token.NewFileSet(),
- pkg: p,
+ pkgs: pkgs,
}
return b, nil
}
diff --git a/cmd/gomobile/bind_androidapp.go b/cmd/gomobile/bind_androidapp.go
index 2003dc6..0694f38 100644
--- a/cmd/gomobile/bind_androidapp.go
+++ b/cmd/gomobile/bind_androidapp.go
@@ -18,7 +18,7 @@
"text/template"
)
-func goAndroidBind(pkg *build.Package) error {
+func goAndroidBind(pkgs []*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)")
}
@@ -28,23 +28,25 @@
// https://golang.org/issue/13234.
androidArgs = []string{"-gcflags=-shared", "-ldflags=-shared"}
}
- typesPkg, err := loadExportData(pkg.ImportPath, androidArmEnv, androidArgs...)
+ typesPkgs, err := loadExportData(pkgs, androidArmEnv, androidArgs...)
if err != nil {
return err
}
- binder, err := newBinder(typesPkg)
+ binder, err := newBinder(typesPkgs)
if err != nil {
return err
}
- if err := binder.GenGo(tmpdir); err != nil {
- return err
+ for _, pkg := range typesPkgs {
+ if err := binder.GenGo(pkg, 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())
+ return androidMainTmpl.Execute(w, binder.pkgs)
})
if err != nil {
return fmt.Errorf("failed to create the main package for android: %v", err)
@@ -68,12 +70,14 @@
}
repo := filepath.Clean(filepath.Join(p.Dir, "..")) // golang.org/x/mobile directory.
- pkgpath := strings.Replace(bindJavaPkg, ".", "/", -1)
- if bindJavaPkg == "" {
- pkgpath = "go/" + binder.pkg.Name()
- }
- if err := binder.GenJava(filepath.Join(androidDir, "src/main/java/"+pkgpath)); err != nil {
- return err
+ for _, pkg := range binder.pkgs {
+ pkgpath := strings.Replace(bindJavaPkg, ".", "/", -1)
+ if bindJavaPkg == "" {
+ pkgpath = "go/" + pkg.Name()
+ }
+ if err := binder.GenJava(pkg, filepath.Join(androidDir, "src/main/java/"+pkgpath)); err != nil {
+ return err
+ }
}
dst := filepath.Join(androidDir, "src/main/java/go/LoadJNI.java")
@@ -92,7 +96,7 @@
return err
}
- return buildAAR(androidDir, pkg)
+ return buildAAR(androidDir, pkgs)
}
var loadSrc = `package go;
@@ -109,7 +113,8 @@
import (
_ "golang.org/x/mobile/bind/java"
- _ "{{.}}"
+{{range .}} _ "../go_{{.Name}}"
+{{end}}
)
func main() {}
@@ -133,10 +138,10 @@
// aidl (optional, not relevant)
//
// javac and jar commands are needed to build classes.jar.
-func buildAAR(androidDir string, pkg *build.Package) (err error) {
+func buildAAR(androidDir string, pkgs []*build.Package) (err error) {
var out io.Writer = ioutil.Discard
if buildO == "" {
- buildO = pkg.Name + ".aar"
+ buildO = pkgs[0].Name + ".aar"
}
if !strings.HasSuffix(buildO, ".aar") {
return fmt.Errorf("output file name %q does not end in '.aar'", buildO)
@@ -167,7 +172,7 @@
}
const manifestFmt = `<manifest xmlns:android="http://schemas.android.com/apk/res/android" package=%q>
<uses-sdk android:minSdkVersion="%d"/></manifest>`
- fmt.Fprintf(w, manifestFmt, "go."+pkg.Name+".gojni", minAndroidAPI)
+ fmt.Fprintf(w, manifestFmt, "go."+pkgs[0].Name+".gojni", minAndroidAPI)
w, err = aarwcreate("proguard.txt")
if err != nil {
@@ -184,39 +189,47 @@
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 {
+ files := map[string]string{}
+ for _, pkg := range pkgs {
+ assetsDir := filepath.Join(pkg.Dir, "assets")
+ assetsDirExists := false
+ if fi, err := os.Stat(assetsDir); err == nil {
+ assetsDirExists = fi.IsDir()
+ } else if !os.IsNotExist(err) {
return err
}
+
+ if assetsDirExists {
+ err := filepath.Walk(
+ assetsDir, func(path string, info os.FileInfo, err error) error {
+ if err != nil {
+ return err
+ }
+ if info.IsDir() {
+ return nil
+ }
+ f, err := os.Open(path)
+ if err != nil {
+ return err
+ }
+ defer f.Close()
+ name := "assets/" + path[len(assetsDir)+1:]
+ if orig, exists := files[name]; exists {
+ return fmt.Errorf("package %s asset name conflict: %s already added from package %s",
+ pkg.ImportPath, name, orig)
+ }
+ files[name] = pkg.ImportPath
+ w, err := aarwcreate(name)
+ if err != nil {
+ return nil
+ }
+ _, err = io.Copy(w, f)
+ return err
+ })
+ if err != nil {
+ return err
+ }
+ }
}
lib := "armeabi-v7a/libgojni.so"
diff --git a/cmd/gomobile/bind_iosapp.go b/cmd/gomobile/bind_iosapp.go
index 85c999f..9947334 100644
--- a/cmd/gomobile/bind_iosapp.go
+++ b/cmd/gomobile/bind_iosapp.go
@@ -15,17 +15,17 @@
"text/template"
)
-func goIOSBind(pkg *build.Package) error {
- typesPkg, err := loadExportData(pkg.ImportPath, darwinArmEnv)
+func goIOSBind(pkgs []*build.Package) error {
+ typesPkgs, err := loadExportData(pkgs, darwinArmEnv)
if err != nil {
return err
}
- binder, err := newBinder(typesPkg)
+ binder, err := newBinder(typesPkgs)
if err != nil {
return err
}
- name := binder.pkg.Name()
+ name := binder.pkgs[0].Name()
title := strings.Title(name)
if buildO != "" && !strings.HasSuffix(buildO, ".framework") {
@@ -35,7 +35,7 @@
buildO = title + ".framework"
}
- if err := binder.GenGo(filepath.Join(tmpdir, "src")); err != nil {
+ if err := binder.GenGo(binder.pkgs[0], filepath.Join(tmpdir, "src")); err != nil {
return err
}
mainFile := filepath.Join(tmpdir, "src/iosbin/main.go")
diff --git a/cmd/gomobile/build.go b/cmd/gomobile/build.go
index d8e7523..052a959 100644
--- a/cmd/gomobile/build.go
+++ b/cmd/gomobile/build.go
@@ -248,14 +248,14 @@
}
func goBuild(src string, env []string, args ...string) error {
- return goCmd("build", src, env, args...)
+ return goCmd("build", []string{src}, env, args...)
}
-func goInstall(src string, env []string, args ...string) error {
- return goCmd("install", src, env, args...)
+func goInstall(srcs []string, env []string, args ...string) error {
+ return goCmd("install", srcs, env, args...)
}
-func goCmd(subcmd, src string, env []string, args ...string) error {
+func goCmd(subcmd string, srcs []string, env []string, args ...string) error {
// The -p flag is to speed up darwin/arm builds.
// Remove when golang.org/issue/10477 is resolved.
cmd := exec.Command(
@@ -284,7 +284,7 @@
cmd.Args = append(cmd.Args, "-work")
}
cmd.Args = append(cmd.Args, args...)
- cmd.Args = append(cmd.Args, src)
+ cmd.Args = append(cmd.Args, srcs...)
cmd.Env = append([]string{}, env...)
return runCmd(cmd)
}