github.com/c-darwin/mobile@v0.0.0-20160313183840-ff625c46f7c9/cmd/gomobile/bind.go (about)

     1  // Copyright 2015 The Go Authors.  All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package main
     6  
     7  import (
     8  	"errors"
     9  	"fmt"
    10  	"go/ast"
    11  	"go/build"
    12  	"go/parser"
    13  	"go/scanner"
    14  	"go/token"
    15  	"go/types"
    16  	"io"
    17  	"io/ioutil"
    18  	"os"
    19  	"path/filepath"
    20  	"strings"
    21  
    22  	"github.com/c-darwin/mobile/bind"
    23  	"github.com/c-darwin/mobile/internal/loader"
    24  )
    25  
    26  // ctx, pkg, tmpdir in build.go
    27  
    28  var cmdBind = &command{
    29  	run:   runBind,
    30  	Name:  "bind",
    31  	Usage: "[-target android|ios] [-o output] [build flags] [package]",
    32  	Short: "build a shared library for android APK and iOS app",
    33  	Long: `
    34  Bind generates language bindings for the package named by the import
    35  path, and compiles a library for the named target system.
    36  
    37  The -target flag takes a target system name, either android (the
    38  default) or ios.
    39  
    40  For -target android, the bind command produces an AAR (Android ARchive)
    41  file that archives the precompiled Java API stub classes, the compiled
    42  shared libraries, and all asset files in the /assets subdirectory under
    43  the package directory. The output is named '<package_name>.aar' by
    44  default. This AAR file is commonly used for binary distribution of an
    45  Android library project and most Android IDEs support AAR import. For
    46  example, in Android Studio (1.2+), an AAR file can be imported using
    47  the module import wizard (File > New > New Module > Import .JAR or
    48  .AAR package), and setting it as a new dependency
    49  (File > Project Structure > Dependencies).  This requires 'javac'
    50  (version 1.7+) and Android SDK (API level 9 or newer) to build the
    51  library for Android. The environment variable ANDROID_HOME must be set
    52  to the path to Android SDK.
    53  
    54  For -target ios, gomobile must be run on an OS X machine with Xcode
    55  installed. Support is not complete.
    56  
    57  The -v flag provides verbose output, including the list of packages built.
    58  
    59  The build flags -a, -i, -n, -x, -gcflags, -ldflags, -tags, and -work
    60  are shared with the build command. For documentation, see 'go help build'.
    61  `,
    62  }
    63  
    64  func runBind(cmd *command) error {
    65  	cleanup, err := buildEnvInit()
    66  	if err != nil {
    67  		return err
    68  	}
    69  	defer cleanup()
    70  
    71  	args := cmd.flag.Args()
    72  
    73  	ctx.GOARCH = "arm"
    74  	switch buildTarget {
    75  	case "android":
    76  		ctx.GOOS = "android"
    77  	case "ios":
    78  		ctx.GOOS = "darwin"
    79  	default:
    80  		return fmt.Errorf(`unknown -target, %q.`, buildTarget)
    81  	}
    82  
    83  	var pkg *build.Package
    84  	switch len(args) {
    85  	case 0:
    86  		pkg, err = ctx.ImportDir(cwd, build.ImportComment)
    87  	case 1:
    88  		pkg, err = ctx.Import(args[0], cwd, build.ImportComment)
    89  	default:
    90  		cmd.usage()
    91  		os.Exit(1)
    92  	}
    93  	if err != nil {
    94  		return err
    95  	}
    96  
    97  	switch buildTarget {
    98  	case "android":
    99  		return goAndroidBind(pkg)
   100  	case "ios":
   101  		return goIOSBind(pkg)
   102  	default:
   103  		return fmt.Errorf(`unknown -target, %q.`, buildTarget)
   104  	}
   105  }
   106  
   107  type binder struct {
   108  	files []*ast.File
   109  	fset  *token.FileSet
   110  	pkg   *types.Package
   111  }
   112  
   113  func (b *binder) GenObjc(outdir string) error {
   114  	name := strings.Title(b.pkg.Name())
   115  	mfile := filepath.Join(outdir, "Go"+name+".m")
   116  	hfile := filepath.Join(outdir, "Go"+name+".h")
   117  
   118  	if buildX {
   119  		printcmd("gobind -lang=objc %s > %s", b.pkg.Path(), mfile)
   120  	}
   121  
   122  	generate := func(w io.Writer) error {
   123  		return bind.GenObjc(w, b.fset, b.pkg, false)
   124  	}
   125  	if err := writeFile(mfile, generate); err != nil {
   126  		return err
   127  	}
   128  	generate = func(w io.Writer) error {
   129  		return bind.GenObjc(w, b.fset, b.pkg, true)
   130  	}
   131  	if err := writeFile(hfile, generate); err != nil {
   132  		return err
   133  	}
   134  
   135  	objcPkg, err := ctx.Import("github.com/c-darwin/mobile/bind/objc", "", build.FindOnly)
   136  	if err != nil {
   137  		return err
   138  	}
   139  	return copyFile(filepath.Join(outdir, "seq.h"), filepath.Join(objcPkg.Dir, "seq.h"))
   140  }
   141  
   142  func (b *binder) GenJava(outdir string) error {
   143  	className := strings.Title(b.pkg.Name())
   144  	javaFile := filepath.Join(outdir, className+".java")
   145  
   146  	if buildX {
   147  		printcmd("gobind -lang=java %s > %s", b.pkg.Path(), javaFile)
   148  	}
   149  
   150  	generate := func(w io.Writer) error {
   151  		return bind.GenJava(w, b.fset, b.pkg)
   152  	}
   153  	if err := writeFile(javaFile, generate); err != nil {
   154  		return err
   155  	}
   156  	return nil
   157  }
   158  
   159  func (b *binder) GenGo(outdir string) error {
   160  	pkgName := "go_" + b.pkg.Name()
   161  	goFile := filepath.Join(outdir, pkgName, pkgName+"main.go")
   162  
   163  	if buildX {
   164  		printcmd("gobind -lang=go %s > %s", b.pkg.Path(), goFile)
   165  	}
   166  
   167  	generate := func(w io.Writer) error {
   168  		return bind.GenGo(w, b.fset, b.pkg)
   169  	}
   170  	if err := writeFile(goFile, generate); err != nil {
   171  		return err
   172  	}
   173  	return nil
   174  }
   175  
   176  func copyFile(dst, src string) error {
   177  	if buildX {
   178  		printcmd("cp %s %s", src, dst)
   179  	}
   180  	return writeFile(dst, func(w io.Writer) error {
   181  		if buildN {
   182  			return nil
   183  		}
   184  		f, err := os.Open(src)
   185  		if err != nil {
   186  			return err
   187  		}
   188  		defer f.Close()
   189  
   190  		if _, err := io.Copy(w, f); err != nil {
   191  			return fmt.Errorf("cp %s %s failed: %v", src, dst, err)
   192  		}
   193  		return nil
   194  	})
   195  }
   196  
   197  func writeFile(filename string, generate func(io.Writer) error) error {
   198  	if buildV {
   199  		fmt.Fprintf(os.Stderr, "write %s\n", filename)
   200  	}
   201  
   202  	err := mkdir(filepath.Dir(filename))
   203  	if err != nil {
   204  		return err
   205  	}
   206  
   207  	if buildN {
   208  		return generate(ioutil.Discard)
   209  	}
   210  
   211  	f, err := os.Create(filename)
   212  	if err != nil {
   213  		return err
   214  	}
   215  	defer func() {
   216  		if cerr := f.Close(); err == nil {
   217  			err = cerr
   218  		}
   219  	}()
   220  
   221  	return generate(f)
   222  }
   223  
   224  func newBinder(bindPkg *build.Package) (*binder, error) {
   225  	if bindPkg.Name == "main" {
   226  		return nil, fmt.Errorf("package %q: can only bind a library package", bindPkg.Name)
   227  	}
   228  
   229  	fset := token.NewFileSet()
   230  
   231  	hasErr := false
   232  	var files []*ast.File
   233  	for _, filename := range bindPkg.GoFiles {
   234  		p := filepath.Join(bindPkg.Dir, filename)
   235  		file, err := parser.ParseFile(fset, p, nil, parser.AllErrors)
   236  		if err != nil {
   237  			hasErr = true
   238  			if list, _ := err.(scanner.ErrorList); len(list) > 0 {
   239  				for _, err := range list {
   240  					fmt.Fprintln(os.Stderr, err)
   241  				}
   242  			} else {
   243  				fmt.Fprintln(os.Stderr, err)
   244  			}
   245  		}
   246  		files = append(files, file)
   247  	}
   248  
   249  	if hasErr {
   250  		return nil, errors.New("package parsing failed.")
   251  	}
   252  
   253  	conf := loader.Config{
   254  		Fset:        fset,
   255  		AllowErrors: true,
   256  	}
   257  	conf.TypeChecker.IgnoreFuncBodies = true
   258  	conf.TypeChecker.FakeImportC = true
   259  	conf.TypeChecker.DisableUnusedImportCheck = true
   260  	var tcErrs []error
   261  	conf.TypeChecker.Error = func(err error) {
   262  		tcErrs = append(tcErrs, err)
   263  	}
   264  
   265  	conf.CreateFromFiles(bindPkg.ImportPath, files...)
   266  	program, err := conf.Load()
   267  	if err != nil {
   268  		for _, err := range tcErrs {
   269  			fmt.Fprintln(os.Stderr, err)
   270  		}
   271  		return nil, err
   272  	}
   273  	b := &binder{
   274  		files: files,
   275  		fset:  fset,
   276  		pkg:   program.Created[0].Pkg,
   277  	}
   278  	return b, nil
   279  }