2
0
mirror of synced 2025-02-22 06:28:04 +00:00

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>
This commit is contained in:
Hyang-Ah (Hana) Kim 2015-08-28 13:39:30 -04:00 committed by Hyang-Ah Hana Kim
parent 00865a642f
commit 4a6caa5fd7
4 changed files with 143 additions and 58 deletions

View File

@ -49,10 +49,12 @@ the module import wizard (File > New > New Module > Import .JAR or
(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 @@ func runBind(cmd *command) error {
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 @@ func runBind(cmd *command) error {
}
}
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 @@ type binder struct {
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) GenObjc(outdir string) error {
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) GenJava(outdir string) error {
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 {

View File

@ -58,8 +58,11 @@ func goAndroidBind(pkg *build.Package) error {
}
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
}

View File

@ -8,6 +8,7 @@ import (
"bytes"
"os"
"path/filepath"
"strings"
"testing"
"text/template"
)
@ -15,52 +16,100 @@ import (
// 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 .
`))

View File

@ -64,20 +64,7 @@ func diffOutput(got string, wantTmpl *template.Template) (string, error) {
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 @@ type outputData struct {
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