cmd/gomobile: bind allows custom package path/prefix.
New option -javapkg for -target=android, and -prefix for -target=ios.
Fixes golang/go#9660.
Change-Id: I9143f30672672527876524b38f450629452a3161
Reviewed-on: https://go-review.googlesource.com/14023
Reviewed-by: David Crawshaw <crawshaw@golang.org>
diff --git a/cmd/gomobile/bind.go b/cmd/gomobile/bind.go
index f1f486f..f76cac6 100644
--- a/cmd/gomobile/bind.go
+++ b/cmd/gomobile/bind.go
@@ -49,10 +49,12 @@
(File > Project Structure > Dependencies). This requires 'javac'
(version 1.7+) and Android SDK (API level 9 or newer) to build the
library for Android. The environment variable ANDROID_HOME must be set
-to the path to Android SDK.
+to the path to Android SDK. The generated Java class is in the java
+package 'go.<package_name>' unless -javapkg flag is specified.
For -target ios, gomobile must be run on an OS X machine with Xcode
-installed. Support is not complete.
+installed. Support is not complete. The generated Objective-C types
+are prefixed with 'Go' unless the -prefix flag is provided.
The -v flag provides verbose output, including the list of packages built.
@@ -80,6 +82,13 @@
return fmt.Errorf(`unknown -target, %q.`, buildTarget)
}
+ if bindJavaPkg != "" && ctx.GOOS != "android" {
+ return fmt.Errorf("-javapkg is supported only for android target")
+ }
+ if bindPrefix != "" && ctx.GOOS != "darwin" {
+ return fmt.Errorf("-prefix is supported only for ios target")
+ }
+
var pkg *build.Package
switch len(args) {
case 0:
@@ -104,6 +113,19 @@
}
}
+var (
+ bindPrefix string // -prefix
+ bindJavaPkg string // -javapkg
+)
+
+func init() {
+ // bind command specific commands.
+ cmdBind.flag.StringVar(&bindJavaPkg, "javapkg", "",
+ "specifies custom Java package path used instead of the default 'go.<go package name>'. Valid only with -target=android.")
+ cmdBind.flag.StringVar(&bindPrefix, "prefix", "",
+ "custom Objective-C name prefix used instead of the default 'Go'. Valid only with -lang=ios.")
+}
+
type binder struct {
files []*ast.File
fset *token.FileSet
@@ -112,23 +134,27 @@
func (b *binder) GenObjc(outdir string) error {
name := strings.Title(b.pkg.Name())
- mfile := filepath.Join(outdir, "Go"+name+".m")
- hfile := filepath.Join(outdir, "Go"+name+".h")
-
- if buildX {
- printcmd("gobind -lang=objc %s > %s", b.pkg.Path(), mfile)
+ bindOption := "-lang=objc"
+ prefix := "Go"
+ if bindPrefix != "" {
+ prefix = bindPrefix
+ bindOption += " -prefix=" + bindPrefix
}
- const objcPrefix = "" // TODO(hyangah): -prefix
+ mfile := filepath.Join(outdir, prefix+name+".m")
+ hfile := filepath.Join(outdir, prefix+name+".h")
generate := func(w io.Writer) error {
- return bind.GenObjc(w, b.fset, b.pkg, objcPrefix, false)
+ if buildX {
+ printcmd("gobind %s -outdir=%s %s", bindOption, outdir, b.pkg.Path())
+ }
+ return bind.GenObjc(w, b.fset, b.pkg, prefix, false)
}
if err := writeFile(mfile, generate); err != nil {
return err
}
generate = func(w io.Writer) error {
- return bind.GenObjc(w, b.fset, b.pkg, objcPrefix, true)
+ return bind.GenObjc(w, b.fset, b.pkg, prefix, true)
}
if err := writeFile(hfile, generate); err != nil {
return err
@@ -144,14 +170,16 @@
func (b *binder) GenJava(outdir string) error {
className := strings.Title(b.pkg.Name())
javaFile := filepath.Join(outdir, className+".java")
-
- if buildX {
- printcmd("gobind -lang=java %s > %s", b.pkg.Path(), javaFile)
+ bindOption := "-lang=java"
+ if bindJavaPkg != "" {
+ bindOption += " -javapkg=" + bindJavaPkg
}
- const javaPkg = "" // TODO(hyangah): -javapkg
generate := func(w io.Writer) error {
- return bind.GenJava(w, b.fset, b.pkg, javaPkg)
+ if buildX {
+ printcmd("gobind %s -outdir=%s %s", bindOption, outdir, b.pkg.Path())
+ }
+ return bind.GenJava(w, b.fset, b.pkg, bindJavaPkg)
}
if err := writeFile(javaFile, generate); err != nil {
return err
@@ -161,13 +189,13 @@
func (b *binder) GenGo(outdir string) error {
pkgName := "go_" + b.pkg.Name()
- goFile := filepath.Join(outdir, pkgName, pkgName+"main.go")
-
- if buildX {
- printcmd("gobind -lang=go %s > %s", b.pkg.Path(), goFile)
- }
+ 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())
+ }
return bind.GenGo(w, b.fset, b.pkg)
}
if err := writeFile(goFile, generate); err != nil {
diff --git a/cmd/gomobile/bind_androidapp.go b/cmd/gomobile/bind_androidapp.go
index 5a639be..667066a 100644
--- a/cmd/gomobile/bind_androidapp.go
+++ b/cmd/gomobile/bind_androidapp.go
@@ -58,8 +58,11 @@
}
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 {
+ 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
}
diff --git a/cmd/gomobile/bind_test.go b/cmd/gomobile/bind_test.go
index 9964d7e..5ab697d 100644
--- a/cmd/gomobile/bind_test.go
+++ b/cmd/gomobile/bind_test.go
@@ -8,6 +8,7 @@
"bytes"
"os"
"path/filepath"
+ "strings"
"testing"
"text/template"
)
@@ -15,52 +16,100 @@
// TODO(crawshaw): TestBindIOS
func TestBindAndroid(t *testing.T) {
- if os.Getenv("ANDROID_HOME") == "" {
+ androidHome := os.Getenv("ANDROID_HOME")
+ if androidHome == "" {
t.Skip("ANDROID_HOME not found, skipping bind")
}
+ platform, err := androidAPIPath()
+ if err != nil {
+ t.Skip("No android API platform found in $ANDROID_HOME, skipping bind")
+ }
+ platform = strings.Replace(platform, androidHome, "$ANDROID_HOME", -1)
- buf := new(bytes.Buffer)
defer func() {
xout = os.Stderr
buildN = false
buildX = false
+ buildO = ""
+ buildTarget = ""
}()
- xout = buf
buildN = true
buildX = true
buildO = "asset.aar"
buildTarget = "android"
- gopath = filepath.SplitList(os.Getenv("GOPATH"))[0]
- if goos == "windows" {
- os.Setenv("HOMEDRIVE", "C:")
- }
- cmdBind.flag.Parse([]string{"golang.org/x/mobile/asset"})
- err := runBind(cmdBind)
- if err != nil {
- t.Log(buf.String())
- t.Fatal(err)
- }
- diff, err := diffOutput(buf.String(), bindAndroidTmpl)
- if err != nil {
- t.Fatalf("computing diff failed: %v", err)
+ tests := []struct {
+ javaPkg string
+ wantGobind string
+ wantPkgDir string
+ }{
+ {
+ wantGobind: "gobind -lang=java",
+ wantPkgDir: "go/asset",
+ },
+ {
+ javaPkg: "com.example.foo",
+ wantGobind: "gobind -lang=java -javapkg=com.example.foo",
+ wantPkgDir: "com/example/foo",
+ },
}
- if diff != "" {
- t.Errorf("unexpected output:\n%s", diff)
+ for _, tc := range tests {
+ bindJavaPkg = tc.javaPkg
+
+ buf := new(bytes.Buffer)
+ xout = buf
+ gopath = filepath.SplitList(os.Getenv("GOPATH"))[0]
+ if goos == "windows" {
+ os.Setenv("HOMEDRIVE", "C:")
+ }
+ cmdBind.flag.Parse([]string{"golang.org/x/mobile/asset"})
+ err := runBind(cmdBind)
+ if err != nil {
+ t.Log(buf.String())
+ t.Fatal(err)
+ }
+ got := filepath.ToSlash(buf.String())
+
+ data := struct {
+ outputData
+ AndroidPlatform string
+ GobindJavaCmd string
+ JavaPkgDir string
+ }{
+ outputData: defaultOutputData(),
+ AndroidPlatform: platform,
+ GobindJavaCmd: tc.wantGobind,
+ JavaPkgDir: tc.wantPkgDir,
+ }
+
+ wantBuf := new(bytes.Buffer)
+ if err := bindAndroidTmpl.Execute(wantBuf, data); err != nil {
+ t.Errorf("%+v: computing diff failed: %v", tc, err)
+ continue
+ }
+
+ diff, err := diff(got, wantBuf.String())
+ if err != nil {
+ t.Errorf("%+v: computing diff failed: %v", tc, err)
+ continue
+ }
+ if diff != "" {
+ t.Errorf("%+v: unexpected output:\n%s", tc, diff)
+ }
}
}
var bindAndroidTmpl = template.Must(template.New("output").Parse(`GOMOBILE={{.GOPATH}}/pkg/gomobile
WORK=$WORK
-gobind -lang=go golang.org/x/mobile/asset > $WORK/go_asset/go_assetmain.go
mkdir -p $WORK/go_asset
+gobind -lang=go -outdir=$WORK/go_asset golang.org/x/mobile/asset
mkdir -p $WORK/androidlib
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}} CGO_ENABLED=1 go build -p={{.NumCPU}} -pkgdir=$GOMOBILE/pkg_android_arm -tags="" -x -buildmode=c-shared -o=$WORK/android/src/main/jniLibs/armeabi-v7a/libgojni.so $WORK/androidlib/main.go
-gobind -lang=java golang.org/x/mobile/asset > $WORK/android/src/main/java/go/asset/Asset.java
-mkdir -p $WORK/android/src/main/java/go/asset
+mkdir -p $WORK/android/src/main/java/{{.JavaPkgDir}}
+{{.GobindJavaCmd}} -outdir=$WORK/android/src/main/java/{{.JavaPkgDir}} golang.org/x/mobile/asset
mkdir -p $WORK/android/src/main/java/go
rm $WORK/android/src/main/java/go/Seq.java
ln -s $GOPATH/src/golang.org/x/mobile/bind/java/Seq.java $WORK/android/src/main/java/go/Seq.java
-PWD=$WORK/android/src/main/java javac -d $WORK/javac-output -source 1.7 -target 1.7 -bootclasspath $ANDROID_HOME/platforms/android-22/android.jar *.java
+PWD=$WORK/android/src/main/java javac -d $WORK/javac-output -source 1.7 -target 1.7 -bootclasspath {{.AndroidPlatform}}/android.jar *.java
jar c -C $WORK/javac-output .
`))
diff --git a/cmd/gomobile/init_test.go b/cmd/gomobile/init_test.go
index e122fb6..8d73a28 100644
--- a/cmd/gomobile/init_test.go
+++ b/cmd/gomobile/init_test.go
@@ -64,20 +64,7 @@
got = filepath.ToSlash(got)
wantBuf := new(bytes.Buffer)
- data := outputData{
- NDK: ndkVersion,
- GOOS: goos,
- GOARCH: goarch,
- GOPATH: gopath,
- NDKARCH: ndkarch,
- Xproj: projPbxproj,
- Xcontents: contentsJSON,
- Xinfo: infoplistTmplData{BundleID: "org.golang.todo.basic", Name: "Basic"},
- NumCPU: strconv.Itoa(runtime.NumCPU()),
- }
- if goos == "windows" {
- data.EXE = ".exe"
- }
+ data := defaultOutputData()
if err := wantTmpl.Execute(wantBuf, data); err != nil {
return "", err
}
@@ -101,6 +88,24 @@
NumCPU string
}
+func defaultOutputData() outputData {
+ data := outputData{
+ NDK: ndkVersion,
+ GOOS: goos,
+ GOARCH: goarch,
+ GOPATH: gopath,
+ NDKARCH: ndkarch,
+ Xproj: projPbxproj,
+ Xcontents: contentsJSON,
+ Xinfo: infoplistTmplData{BundleID: "org.golang.todo.basic", Name: "Basic"},
+ NumCPU: strconv.Itoa(runtime.NumCPU()),
+ }
+ if goos == "windows" {
+ data.EXE = ".exe"
+ }
+ return data
+}
+
var initTmpl = template.Must(template.New("output").Parse(`GOMOBILE={{.GOPATH}}/pkg/gomobile
mkdir -p $GOMOBILE/android-{{.NDK}}
WORK={{.GOPATH}}/pkg/gomobile/work