github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/mobile/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  	"fmt"
     9  	"go/ast"
    10  	"go/build"
    11  	"go/importer"
    12  	"go/token"
    13  	"go/types"
    14  	"io"
    15  	"io/ioutil"
    16  	"os"
    17  	"path"
    18  	"path/filepath"
    19  	"strings"
    20  
    21  	"golang.org/x/mobile/bind"
    22  )
    23  
    24  // ctx, pkg, tmpdir in build.go
    25  
    26  var cmdBind = &command{
    27  	run:   runBind,
    28  	Name:  "bind",
    29  	Usage: "[-target android|ios] [-o output] [build flags] [package]",
    30  	Short: "build a library for Android and iOS",
    31  	Long: `
    32  Bind generates language bindings for the package named by the import
    33  path, and compiles a library for the named target system.
    34  
    35  The -target flag takes a target system name, either android (the
    36  default) or ios.
    37  
    38  For -target android, the bind command produces an AAR (Android ARchive)
    39  file that archives the precompiled Java API stub classes, the compiled
    40  shared libraries, and all asset files in the /assets subdirectory under
    41  the package directory. The output is named '<package_name>.aar' by
    42  default. This AAR file is commonly used for binary distribution of an
    43  Android library project and most Android IDEs support AAR import. For
    44  example, in Android Studio (1.2+), an AAR file can be imported using
    45  the module import wizard (File > New > New Module > Import .JAR or
    46  .AAR package), and setting it as a new dependency
    47  (File > Project Structure > Dependencies).  This requires 'javac'
    48  (version 1.7+) and Android SDK (API level 9 or newer) to build the
    49  library for Android. The environment variable ANDROID_HOME must be set
    50  to the path to Android SDK. The generated Java class is in the java
    51  package 'go.<package_name>' unless -javapkg flag is specified.
    52  
    53  For -target ios, gomobile must be run on an OS X machine with Xcode
    54  installed. Support is not complete. The generated Objective-C types
    55  are prefixed with 'Go' unless the -prefix flag is provided.
    56  
    57  The -v flag provides verbose output, including the list of packages built.
    58  
    59  The build flags -a, -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  	targetOS, targetArchs, err := parseBuildTarget(buildTarget)
    74  	if err != nil {
    75  		return fmt.Errorf(`invalid -target=%q: %v`, buildTarget, err)
    76  	}
    77  
    78  	ctx.GOARCH = "arm"
    79  	ctx.GOOS = targetOS
    80  
    81  	if bindJavaPkg != "" && ctx.GOOS != "android" {
    82  		return fmt.Errorf("-javapkg is supported only for android target")
    83  	}
    84  	if bindPrefix != "" && ctx.GOOS != "darwin" {
    85  		return fmt.Errorf("-prefix is supported only for ios target")
    86  	}
    87  
    88  	var pkgs []*build.Package
    89  	switch len(args) {
    90  	case 0:
    91  		pkgs = make([]*build.Package, 1)
    92  		pkgs[0], err = ctx.ImportDir(cwd, build.ImportComment)
    93  	default:
    94  		pkgs, err = importPackages(args)
    95  	}
    96  	if err != nil {
    97  		return err
    98  	}
    99  
   100  	switch targetOS {
   101  	case "android":
   102  		return goAndroidBind(pkgs, targetArchs)
   103  	case "darwin":
   104  		// TODO: use targetArchs?
   105  		return goIOSBind(pkgs)
   106  	default:
   107  		return fmt.Errorf(`invalid -target=%q`, buildTarget)
   108  	}
   109  }
   110  
   111  func importPackages(args []string) ([]*build.Package, error) {
   112  	pkgs := make([]*build.Package, len(args))
   113  	for i, path := range args {
   114  		var err error
   115  		if pkgs[i], err = ctx.Import(path, cwd, build.ImportComment); err != nil {
   116  			return nil, fmt.Errorf("package %q: %v", path, err)
   117  		}
   118  	}
   119  	return pkgs, nil
   120  }
   121  
   122  var (
   123  	bindPrefix  string // -prefix
   124  	bindJavaPkg string // -javapkg
   125  )
   126  
   127  func init() {
   128  	// bind command specific commands.
   129  	cmdBind.flag.StringVar(&bindJavaPkg, "javapkg", "",
   130  		"specifies custom Java package path used instead of the default 'go.<go package name>'. Valid only with -target=android.")
   131  	cmdBind.flag.StringVar(&bindPrefix, "prefix", "",
   132  		"custom Objective-C name prefix used instead of the default 'Go'. Valid only with -lang=ios.")
   133  }
   134  
   135  type binder struct {
   136  	files []*ast.File
   137  	fset  *token.FileSet
   138  	pkgs  []*types.Package
   139  }
   140  
   141  func (b *binder) GenObjc(pkg *types.Package, outdir string) (string, error) {
   142  	const bindPrefixDefault = "Go"
   143  	if bindPrefix == "" {
   144  		bindPrefix = bindPrefixDefault
   145  	}
   146  	name := strings.Title(pkg.Name())
   147  	bindOption := "-lang=objc"
   148  	if bindPrefix != bindPrefixDefault {
   149  		bindOption += " -prefix=" + bindPrefix
   150  	}
   151  
   152  	fileBase := bindPrefix + name
   153  	mfile := filepath.Join(outdir, fileBase+".m")
   154  	hfile := filepath.Join(outdir, fileBase+".h")
   155  
   156  	generate := func(w io.Writer) error {
   157  		if buildX {
   158  			printcmd("gobind %s -outdir=%s %s", bindOption, outdir, pkg.Path())
   159  		}
   160  		if buildN {
   161  			return nil
   162  		}
   163  		return bind.GenObjc(w, b.fset, pkg, bindPrefix, false)
   164  	}
   165  	if err := writeFile(mfile, generate); err != nil {
   166  		return "", err
   167  	}
   168  	generate = func(w io.Writer) error {
   169  		if buildN {
   170  			return nil
   171  		}
   172  		return bind.GenObjc(w, b.fset, pkg, bindPrefix, true)
   173  	}
   174  	if err := writeFile(hfile, generate); err != nil {
   175  		return "", err
   176  	}
   177  
   178  	objcPkg, err := ctx.Import("golang.org/x/mobile/bind/objc", "", build.FindOnly)
   179  	if err != nil {
   180  		return "", err
   181  	}
   182  	if err := copyFile(filepath.Join(outdir, "seq.h"), filepath.Join(objcPkg.Dir, "seq.h")); err != nil {
   183  		return "", err
   184  	}
   185  	return fileBase, nil
   186  }
   187  
   188  func (b *binder) GenJava(pkg *types.Package, outdir string) error {
   189  	className := strings.Title(pkg.Name())
   190  	javaFile := filepath.Join(outdir, className+".java")
   191  	bindOption := "-lang=java"
   192  	if bindJavaPkg != "" {
   193  		bindOption += " -javapkg=" + bindJavaPkg
   194  	}
   195  
   196  	generate := func(w io.Writer) error {
   197  		if buildX {
   198  			printcmd("gobind %s -outdir=%s %s", bindOption, outdir, pkg.Path())
   199  		}
   200  		if buildN {
   201  			return nil
   202  		}
   203  		return bind.GenJava(w, b.fset, pkg, bindJavaPkg)
   204  	}
   205  	if err := writeFile(javaFile, generate); err != nil {
   206  		return err
   207  	}
   208  	return nil
   209  }
   210  
   211  func (b *binder) GenGo(pkg *types.Package, outdir string) error {
   212  	pkgName := "go_" + pkg.Name()
   213  	outdir = filepath.Join(outdir, pkgName)
   214  	goFile := filepath.Join(outdir, pkgName+"main.go")
   215  
   216  	generate := func(w io.Writer) error {
   217  		if buildX {
   218  			printcmd("gobind -lang=go -outdir=%s %s", outdir, pkg.Path())
   219  		}
   220  		if buildN {
   221  			return nil
   222  		}
   223  		return bind.GenGo(w, b.fset, pkg)
   224  	}
   225  	if err := writeFile(goFile, generate); err != nil {
   226  		return err
   227  	}
   228  	return nil
   229  }
   230  
   231  func copyFile(dst, src string) error {
   232  	if buildX {
   233  		printcmd("cp %s %s", src, dst)
   234  	}
   235  	return writeFile(dst, func(w io.Writer) error {
   236  		if buildN {
   237  			return nil
   238  		}
   239  		f, err := os.Open(src)
   240  		if err != nil {
   241  			return err
   242  		}
   243  		defer f.Close()
   244  
   245  		if _, err := io.Copy(w, f); err != nil {
   246  			return fmt.Errorf("cp %s %s failed: %v", src, dst, err)
   247  		}
   248  		return nil
   249  	})
   250  }
   251  
   252  func writeFile(filename string, generate func(io.Writer) error) error {
   253  	if buildV {
   254  		fmt.Fprintf(os.Stderr, "write %s\n", filename)
   255  	}
   256  
   257  	err := mkdir(filepath.Dir(filename))
   258  	if err != nil {
   259  		return err
   260  	}
   261  
   262  	if buildN {
   263  		return generate(ioutil.Discard)
   264  	}
   265  
   266  	f, err := os.Create(filename)
   267  	if err != nil {
   268  		return err
   269  	}
   270  	defer func() {
   271  		if cerr := f.Close(); err == nil {
   272  			err = cerr
   273  		}
   274  	}()
   275  
   276  	return generate(f)
   277  }
   278  
   279  func loadExportData(pkgs []*build.Package, env []string, args ...string) ([]*types.Package, error) {
   280  	// Compile the package. This will produce good errors if the package
   281  	// doesn't typecheck for some reason, and is a necessary step to
   282  	// building the final output anyway.
   283  	paths := make([]string, len(pkgs))
   284  	for i, p := range pkgs {
   285  		paths[i] = p.ImportPath
   286  	}
   287  	if err := goInstall(paths, env, args...); err != nil {
   288  		return nil, err
   289  	}
   290  
   291  	goos, goarch := getenv(env, "GOOS"), getenv(env, "GOARCH")
   292  
   293  	// Assemble a fake GOPATH and trick go/importer into using it.
   294  	// Ideally the importer package would let us provide this to
   295  	// it somehow, but this works with what's in Go 1.5 today and
   296  	// gives us access to the gcimporter package without us having
   297  	// to make a copy of it.
   298  	fakegopath := filepath.Join(tmpdir, "fakegopath")
   299  	if err := removeAll(fakegopath); err != nil {
   300  		return nil, err
   301  	}
   302  	if err := mkdir(filepath.Join(fakegopath, "pkg")); err != nil {
   303  		return nil, err
   304  	}
   305  	typePkgs := make([]*types.Package, len(pkgs))
   306  	for i, p := range pkgs {
   307  		importPath := p.ImportPath
   308  		src := filepath.Join(pkgdir(env), importPath+".a")
   309  		dst := filepath.Join(fakegopath, "pkg/"+goos+"_"+goarch+"/"+importPath+".a")
   310  		if err := copyFile(dst, src); err != nil {
   311  			return nil, err
   312  		}
   313  		if buildN {
   314  			typePkgs[i] = types.NewPackage(importPath, path.Base(importPath))
   315  			continue
   316  		}
   317  		oldDefault := build.Default
   318  		build.Default = ctx // copy
   319  		build.Default.GOARCH = goarch
   320  		build.Default.GOPATH = fakegopath
   321  		p, err := importer.Default().Import(importPath)
   322  		build.Default = oldDefault
   323  		if err != nil {
   324  			return nil, err
   325  		}
   326  		typePkgs[i] = p
   327  	}
   328  	return typePkgs, nil
   329  }
   330  
   331  func newBinder(pkgs []*types.Package) (*binder, error) {
   332  	for _, pkg := range pkgs {
   333  		if pkg.Name() == "main" {
   334  			return nil, fmt.Errorf("package %q (%q): can only bind a library package", pkg.Name(), pkg.Path())
   335  		}
   336  	}
   337  	b := &binder{
   338  		fset: token.NewFileSet(),
   339  		pkgs: pkgs,
   340  	}
   341  	return b, nil
   342  }