github.com/acrespo/mobile@v0.0.0-20190107162257-dc0771356504/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/build"
    11  	"io"
    12  	"io/ioutil"
    13  	"os"
    14  	"os/exec"
    15  	"path"
    16  	"path/filepath"
    17  )
    18  
    19  // ctx, pkg, tmpdir in build.go
    20  
    21  var cmdBind = &command{
    22  	run:   runBind,
    23  	Name:  "bind",
    24  	Usage: "[-target android|ios] [-bootclasspath <path>] [-classpath <path>] [-o output] [build flags] [package]",
    25  	Short: "build a library for Android and iOS",
    26  	Long: `
    27  Bind generates language bindings for the package named by the import
    28  path, and compiles a library for the named target system.
    29  
    30  The -target flag takes a target system name, either android (the
    31  default) or ios.
    32  
    33  For -target android, the bind command produces an AAR (Android ARchive)
    34  file that archives the precompiled Java API stub classes, the compiled
    35  shared libraries, and all asset files in the /assets subdirectory under
    36  the package directory. The output is named '<package_name>.aar' by
    37  default. This AAR file is commonly used for binary distribution of an
    38  Android library project and most Android IDEs support AAR import. For
    39  example, in Android Studio (1.2+), an AAR file can be imported using
    40  the module import wizard (File > New > New Module > Import .JAR or
    41  .AAR package), and setting it as a new dependency
    42  (File > Project Structure > Dependencies).  This requires 'javac'
    43  (version 1.7+) and Android SDK (API level 15 or newer) to build the
    44  library for Android. The environment variable ANDROID_HOME must be set
    45  to the path to Android SDK. Use the -javapkg flag to specify the Java
    46  package prefix for the generated classes.
    47  
    48  By default, -target=android builds shared libraries for all supported
    49  instruction sets (arm, arm64, 386, amd64). A subset of instruction sets
    50  can be selected by specifying target type with the architecture name. E.g.,
    51  -target=android/arm,android/386.
    52  
    53  For -target ios, gomobile must be run on an OS X machine with Xcode
    54  installed. The generated Objective-C types can be prefixed with the -prefix
    55  flag.
    56  
    57  For -target android, the -bootclasspath and -classpath flags are used to
    58  control the bootstrap classpath and the classpath for Go wrappers to Java
    59  classes.
    60  
    61  The -v flag provides verbose output, including the list of packages built.
    62  
    63  The build flags -a, -n, -x, -gcflags, -ldflags, -tags, and -work
    64  are shared with the build command. For documentation, see 'go help build'.
    65  `,
    66  }
    67  
    68  func runBind(cmd *command) error {
    69  	cleanup, err := buildEnvInit()
    70  	if err != nil {
    71  		return err
    72  	}
    73  	defer cleanup()
    74  
    75  	args := cmd.flag.Args()
    76  
    77  	targetOS, targetArchs, err := parseBuildTarget(buildTarget)
    78  	if err != nil {
    79  		return fmt.Errorf(`invalid -target=%q: %v`, buildTarget, err)
    80  	}
    81  
    82  	oldCtx := ctx
    83  	defer func() {
    84  		ctx = oldCtx
    85  	}()
    86  	ctx.GOARCH = "arm"
    87  	ctx.GOOS = targetOS
    88  
    89  	if bindJavaPkg != "" && ctx.GOOS != "android" {
    90  		return fmt.Errorf("-javapkg is supported only for android target")
    91  	}
    92  	if bindPrefix != "" && ctx.GOOS != "darwin" {
    93  		return fmt.Errorf("-prefix is supported only for ios target")
    94  	}
    95  
    96  	if ctx.GOOS == "android" && !hasNDK() {
    97  		return errors.New("no Android NDK path is set. Please run gomobile init with the ndk-bundle installed through the Android SDK manager or with the -ndk flag set.")
    98  	}
    99  
   100  	if ctx.GOOS == "darwin" {
   101  		ctx.BuildTags = append(ctx.BuildTags, "ios")
   102  	}
   103  
   104  	var gobind string
   105  	if !buildN {
   106  		gobind, err = exec.LookPath("gobind")
   107  		if err != nil {
   108  			return errors.New("gobind was not found. Please run gomobile init before trying again.")
   109  		}
   110  	} else {
   111  		gobind = "gobind"
   112  	}
   113  
   114  	var pkgs []*build.Package
   115  	switch len(args) {
   116  	case 0:
   117  		pkgs = make([]*build.Package, 1)
   118  		pkgs[0], err = ctx.ImportDir(cwd, build.ImportComment)
   119  	default:
   120  		pkgs, err = importPackages(args)
   121  	}
   122  	if err != nil {
   123  		return err
   124  	}
   125  
   126  	// check if any of the package is main
   127  	for _, pkg := range pkgs {
   128  		if pkg.Name == "main" {
   129  			return fmt.Errorf("binding 'main' package (%s) is not supported", pkg.ImportComment)
   130  		}
   131  	}
   132  
   133  	switch targetOS {
   134  	case "android":
   135  		return goAndroidBind(gobind, pkgs, targetArchs)
   136  	case "darwin":
   137  		if !xcodeAvailable() {
   138  			return fmt.Errorf("-target=ios requires XCode")
   139  		}
   140  		return goIOSBind(gobind, pkgs, targetArchs)
   141  	default:
   142  		return fmt.Errorf(`invalid -target=%q`, buildTarget)
   143  	}
   144  }
   145  
   146  func importPackages(args []string) ([]*build.Package, error) {
   147  	pkgs := make([]*build.Package, len(args))
   148  	for i, a := range args {
   149  		a = path.Clean(a)
   150  		var err error
   151  		if pkgs[i], err = ctx.Import(a, cwd, build.ImportComment); err != nil {
   152  			return nil, fmt.Errorf("package %q: %v", a, err)
   153  		}
   154  	}
   155  	return pkgs, nil
   156  }
   157  
   158  var (
   159  	bindPrefix        string // -prefix
   160  	bindJavaPkg       string // -javapkg
   161  	bindClasspath     string // -classpath
   162  	bindBootClasspath string // -bootclasspath
   163  )
   164  
   165  func init() {
   166  	// bind command specific commands.
   167  	cmdBind.flag.StringVar(&bindJavaPkg, "javapkg", "",
   168  		"specifies custom Java package path prefix. Valid only with -target=android.")
   169  	cmdBind.flag.StringVar(&bindPrefix, "prefix", "",
   170  		"custom Objective-C name prefix. Valid only with -target=ios.")
   171  	cmdBind.flag.StringVar(&bindClasspath, "classpath", "", "The classpath for imported Java classes. Valid only with -target=android.")
   172  	cmdBind.flag.StringVar(&bindBootClasspath, "bootclasspath", "", "The bootstrap classpath for imported Java classes. Valid only with -target=android.")
   173  }
   174  
   175  func bootClasspath() (string, error) {
   176  	if bindBootClasspath != "" {
   177  		return bindBootClasspath, nil
   178  	}
   179  	apiPath, err := androidAPIPath()
   180  	if err != nil {
   181  		return "", err
   182  	}
   183  	return filepath.Join(apiPath, "android.jar"), nil
   184  }
   185  
   186  func copyFile(dst, src string) error {
   187  	if buildX {
   188  		printcmd("cp %s %s", src, dst)
   189  	}
   190  	return writeFile(dst, func(w io.Writer) error {
   191  		if buildN {
   192  			return nil
   193  		}
   194  		f, err := os.Open(src)
   195  		if err != nil {
   196  			return err
   197  		}
   198  		defer f.Close()
   199  
   200  		if _, err := io.Copy(w, f); err != nil {
   201  			return fmt.Errorf("cp %s %s failed: %v", src, dst, err)
   202  		}
   203  		return nil
   204  	})
   205  }
   206  
   207  func writeFile(filename string, generate func(io.Writer) error) error {
   208  	if buildV {
   209  		fmt.Fprintf(os.Stderr, "write %s\n", filename)
   210  	}
   211  
   212  	err := mkdir(filepath.Dir(filename))
   213  	if err != nil {
   214  		return err
   215  	}
   216  
   217  	if buildN {
   218  		return generate(ioutil.Discard)
   219  	}
   220  
   221  	f, err := os.Create(filename)
   222  	if err != nil {
   223  		return err
   224  	}
   225  	defer func() {
   226  		if cerr := f.Close(); err == nil {
   227  			err = cerr
   228  		}
   229  	}()
   230  
   231  	return generate(f)
   232  }