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>
311 lines
7.5 KiB
Go
311 lines
7.5 KiB
Go
// 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"
|
|
"go/types"
|
|
"io"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"golang.org/x/mobile/bind"
|
|
"golang.org/x/mobile/internal/loader"
|
|
)
|
|
|
|
// ctx, pkg, tmpdir in build.go
|
|
|
|
var cmdBind = &command{
|
|
run: runBind,
|
|
Name: "bind",
|
|
Usage: "[-target android|ios] [-o output] [build flags] [package]",
|
|
Short: "build a library for Android and iOS",
|
|
Long: `
|
|
Bind generates language bindings for the package named by the import
|
|
path, and compiles a library for the named target system.
|
|
|
|
The -target flag takes a target system name, either android (the
|
|
default) or ios.
|
|
|
|
For -target android, the bind command produces an AAR (Android ARchive)
|
|
file that archives the precompiled Java API stub classes, the compiled
|
|
shared libraries, and all asset files in the /assets subdirectory under
|
|
the package directory. The output is named '<package_name>.aar' by
|
|
default. This AAR file is commonly used for binary distribution of an
|
|
Android library project and most Android IDEs support AAR import. For
|
|
example, in Android Studio (1.2+), an AAR file can be imported using
|
|
the module import wizard (File > New > New Module > Import .JAR or
|
|
.AAR package), and setting it as a new dependency
|
|
(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. 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. 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.
|
|
|
|
The build flags -a, -i, -n, -x, -gcflags, -ldflags, -tags, and -work
|
|
are shared with the build command. For documentation, see 'go help build'.
|
|
`,
|
|
}
|
|
|
|
func runBind(cmd *command) error {
|
|
cleanup, err := buildEnvInit()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer cleanup()
|
|
|
|
args := cmd.flag.Args()
|
|
|
|
ctx.GOARCH = "arm"
|
|
switch buildTarget {
|
|
case "android":
|
|
ctx.GOOS = "android"
|
|
case "ios":
|
|
ctx.GOOS = "darwin"
|
|
default:
|
|
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:
|
|
pkg, err = ctx.ImportDir(cwd, build.ImportComment)
|
|
case 1:
|
|
pkg, err = ctx.Import(args[0], cwd, build.ImportComment)
|
|
default:
|
|
cmd.usage()
|
|
os.Exit(1)
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
switch buildTarget {
|
|
case "android":
|
|
return goAndroidBind(pkg)
|
|
case "ios":
|
|
return goIOSBind(pkg)
|
|
default:
|
|
return fmt.Errorf(`unknown -target, %q.`, buildTarget)
|
|
}
|
|
}
|
|
|
|
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
|
|
pkg *types.Package
|
|
}
|
|
|
|
func (b *binder) GenObjc(outdir string) error {
|
|
name := strings.Title(b.pkg.Name())
|
|
bindOption := "-lang=objc"
|
|
prefix := "Go"
|
|
if bindPrefix != "" {
|
|
prefix = bindPrefix
|
|
bindOption += " -prefix=" + bindPrefix
|
|
}
|
|
|
|
mfile := filepath.Join(outdir, prefix+name+".m")
|
|
hfile := filepath.Join(outdir, prefix+name+".h")
|
|
|
|
generate := func(w io.Writer) error {
|
|
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, prefix, true)
|
|
}
|
|
if err := writeFile(hfile, generate); err != nil {
|
|
return err
|
|
}
|
|
|
|
objcPkg, err := ctx.Import("golang.org/x/mobile/bind/objc", "", build.FindOnly)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
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())
|
|
javaFile := filepath.Join(outdir, className+".java")
|
|
bindOption := "-lang=java"
|
|
if bindJavaPkg != "" {
|
|
bindOption += " -javapkg=" + bindJavaPkg
|
|
}
|
|
|
|
generate := func(w io.Writer) error {
|
|
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
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (b *binder) GenGo(outdir string) error {
|
|
pkgName := "go_" + b.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())
|
|
}
|
|
return bind.GenGo(w, b.fset, b.pkg)
|
|
}
|
|
if err := writeFile(goFile, generate); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func copyFile(dst, src string) error {
|
|
if buildX {
|
|
printcmd("cp %s %s", src, dst)
|
|
}
|
|
return writeFile(dst, func(w io.Writer) error {
|
|
if buildN {
|
|
return nil
|
|
}
|
|
f, err := os.Open(src)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer f.Close()
|
|
|
|
if _, err := io.Copy(w, f); err != nil {
|
|
return fmt.Errorf("cp %s %s failed: %v", src, dst, err)
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func writeFile(filename string, generate func(io.Writer) error) error {
|
|
if buildV {
|
|
fmt.Fprintf(os.Stderr, "write %s\n", filename)
|
|
}
|
|
|
|
err := mkdir(filepath.Dir(filename))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if buildN {
|
|
return generate(ioutil.Discard)
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
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{
|
|
Fset: fset,
|
|
AllowErrors: true,
|
|
}
|
|
conf.TypeChecker.IgnoreFuncBodies = true
|
|
conf.TypeChecker.FakeImportC = true
|
|
conf.TypeChecker.DisableUnusedImportCheck = true
|
|
var tcErrs []error
|
|
conf.TypeChecker.Error = func(err error) {
|
|
tcErrs = append(tcErrs, err)
|
|
}
|
|
|
|
conf.CreateFromFiles(bindPkg.ImportPath, files...)
|
|
program, err := conf.Load()
|
|
if err != nil {
|
|
for _, err := range tcErrs {
|
|
fmt.Fprintln(os.Stderr, err)
|
|
}
|
|
return nil, err
|
|
}
|
|
b := &binder{
|
|
files: files,
|
|
fset: fset,
|
|
pkg: program.Created[0].Pkg,
|
|
}
|
|
return b, nil
|
|
}
|