github.com/SkycoinProject/gomobile@v0.0.0-20190312151609-d3739f865fa6/cmd/gomobile/bind_androidapp.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  	"archive/zip"
     9  	"fmt"
    10  	"go/build"
    11  	"io"
    12  	"io/ioutil"
    13  	"os"
    14  	"os/exec"
    15  	"path/filepath"
    16  	"strconv"
    17  	"strings"
    18  )
    19  
    20  func goAndroidBind(gobind string, pkgs []*build.Package, androidArchs []string) error {
    21  	if sdkDir := os.Getenv("ANDROID_HOME"); sdkDir == "" {
    22  		return fmt.Errorf("this command requires ANDROID_HOME environment variable (path to the Android SDK)")
    23  	}
    24  
    25  	// Run gobind to generate the bindings
    26  	cmd := exec.Command(
    27  		gobind,
    28  		"-lang=go,java",
    29  		"-outdir="+tmpdir,
    30  	)
    31  	cmd.Env = append(cmd.Env, "GOOS=android")
    32  	cmd.Env = append(cmd.Env, "CGO_ENABLED=1")
    33  	if len(ctx.BuildTags) > 0 {
    34  		cmd.Args = append(cmd.Args, "-tags="+strings.Join(ctx.BuildTags, ","))
    35  	}
    36  	if bindJavaPkg != "" {
    37  		cmd.Args = append(cmd.Args, "-javapkg="+bindJavaPkg)
    38  	}
    39  	if bindClasspath != "" {
    40  		cmd.Args = append(cmd.Args, "-classpath="+bindClasspath)
    41  	}
    42  	if bindBootClasspath != "" {
    43  		cmd.Args = append(cmd.Args, "-bootclasspath="+bindBootClasspath)
    44  	}
    45  	for _, p := range pkgs {
    46  		cmd.Args = append(cmd.Args, p.ImportPath)
    47  	}
    48  	if err := runCmd(cmd); err != nil {
    49  		return err
    50  	}
    51  
    52  	androidDir := filepath.Join(tmpdir, "android")
    53  
    54  	// Generate binding code and java source code only when processing the first package.
    55  	for _, arch := range androidArchs {
    56  		env := androidEnv[arch]
    57  		// Add the generated packages to GOPATH
    58  		gopath := fmt.Sprintf("GOPATH=%s%c%s", tmpdir, filepath.ListSeparator, goEnv("GOPATH"))
    59  		env = append(env, gopath)
    60  		toolchain := ndk.Toolchain(arch)
    61  
    62  		err := goBuild(
    63  			"gobind",
    64  			env,
    65  			"-buildmode=c-shared",
    66  			"-o="+filepath.Join(androidDir, "src/main/jniLibs/"+toolchain.abi+"/libgojni.so"),
    67  		)
    68  		if err != nil {
    69  			return err
    70  		}
    71  	}
    72  
    73  	jsrc := filepath.Join(tmpdir, "java")
    74  	if err := buildAAR(jsrc, androidDir, pkgs, androidArchs); err != nil {
    75  		return err
    76  	}
    77  	return buildSrcJar(jsrc)
    78  }
    79  
    80  func buildSrcJar(src string) error {
    81  	var out io.Writer = ioutil.Discard
    82  	if !buildN {
    83  		ext := filepath.Ext(buildO)
    84  		f, err := os.Create(buildO[:len(buildO)-len(ext)] + "-sources.jar")
    85  		if err != nil {
    86  			return err
    87  		}
    88  		defer func() {
    89  			if cerr := f.Close(); err == nil {
    90  				err = cerr
    91  			}
    92  		}()
    93  		out = f
    94  	}
    95  
    96  	return writeJar(out, src)
    97  }
    98  
    99  // AAR is the format for the binary distribution of an Android Library Project
   100  // and it is a ZIP archive with extension .aar.
   101  // http://tools.android.com/tech-docs/new-build-system/aar-format
   102  //
   103  // These entries are directly at the root of the archive.
   104  //
   105  //	AndroidManifest.xml (mandatory)
   106  // 	classes.jar (mandatory)
   107  //	assets/ (optional)
   108  //	jni/<abi>/libgojni.so
   109  //	R.txt (mandatory)
   110  //	res/ (mandatory)
   111  //	libs/*.jar (optional, not relevant)
   112  //	proguard.txt (optional)
   113  //	lint.jar (optional, not relevant)
   114  //	aidl (optional, not relevant)
   115  //
   116  // javac and jar commands are needed to build classes.jar.
   117  func buildAAR(srcDir, androidDir string, pkgs []*build.Package, androidArchs []string) (err error) {
   118  	var out io.Writer = ioutil.Discard
   119  	if buildO == "" {
   120  		buildO = pkgs[0].Name + ".aar"
   121  	}
   122  	if !strings.HasSuffix(buildO, ".aar") {
   123  		return fmt.Errorf("output file name %q does not end in '.aar'", buildO)
   124  	}
   125  	if !buildN {
   126  		f, err := os.Create(buildO)
   127  		if err != nil {
   128  			return err
   129  		}
   130  		defer func() {
   131  			if cerr := f.Close(); err == nil {
   132  				err = cerr
   133  			}
   134  		}()
   135  		out = f
   136  	}
   137  
   138  	aarw := zip.NewWriter(out)
   139  	aarwcreate := func(name string) (io.Writer, error) {
   140  		if buildV {
   141  			fmt.Fprintf(os.Stderr, "aar: %s\n", name)
   142  		}
   143  		return aarw.Create(name)
   144  	}
   145  	w, err := aarwcreate("AndroidManifest.xml")
   146  	if err != nil {
   147  		return err
   148  	}
   149  	const manifestFmt = `<manifest xmlns:android="http://schemas.android.com/apk/res/android" package=%q>
   150  <uses-sdk android:minSdkVersion="%d"/></manifest>`
   151  	fmt.Fprintf(w, manifestFmt, "go."+pkgs[0].Name+".gojni", minAndroidAPI)
   152  
   153  	w, err = aarwcreate("proguard.txt")
   154  	if err != nil {
   155  		return err
   156  	}
   157  	fmt.Fprintln(w, `-keep class go.** { *; }`)
   158  	if bindJavaPkg != "" {
   159  		fmt.Fprintln(w, `-keep class `+bindJavaPkg+`.** { *; }`)
   160  	} else {
   161  		for _, p := range pkgs {
   162  			fmt.Fprintln(w, `-keep class `+p.Name+`.** { *; }`)
   163  		}
   164  	}
   165  
   166  	w, err = aarwcreate("classes.jar")
   167  	if err != nil {
   168  		return err
   169  	}
   170  	if err := buildJar(w, srcDir); err != nil {
   171  		return err
   172  	}
   173  
   174  	files := map[string]string{}
   175  	for _, pkg := range pkgs {
   176  		assetsDir := filepath.Join(pkg.Dir, "assets")
   177  		assetsDirExists := false
   178  		if fi, err := os.Stat(assetsDir); err == nil {
   179  			assetsDirExists = fi.IsDir()
   180  		} else if !os.IsNotExist(err) {
   181  			return err
   182  		}
   183  
   184  		if assetsDirExists {
   185  			err := filepath.Walk(
   186  				assetsDir, func(path string, info os.FileInfo, err error) error {
   187  					if err != nil {
   188  						return err
   189  					}
   190  					if info.IsDir() {
   191  						return nil
   192  					}
   193  					f, err := os.Open(path)
   194  					if err != nil {
   195  						return err
   196  					}
   197  					defer f.Close()
   198  					name := "assets/" + path[len(assetsDir)+1:]
   199  					if orig, exists := files[name]; exists {
   200  						return fmt.Errorf("package %s asset name conflict: %s already added from package %s",
   201  							pkg.ImportPath, name, orig)
   202  					}
   203  					files[name] = pkg.ImportPath
   204  					w, err := aarwcreate(name)
   205  					if err != nil {
   206  						return nil
   207  					}
   208  					_, err = io.Copy(w, f)
   209  					return err
   210  				})
   211  			if err != nil {
   212  				return err
   213  			}
   214  		}
   215  	}
   216  
   217  	for _, arch := range androidArchs {
   218  		toolchain := ndk.Toolchain(arch)
   219  		lib := toolchain.abi + "/libgojni.so"
   220  		w, err = aarwcreate("jni/" + lib)
   221  		if err != nil {
   222  			return err
   223  		}
   224  		if !buildN {
   225  			r, err := os.Open(filepath.Join(androidDir, "src/main/jniLibs/"+lib))
   226  			if err != nil {
   227  				return err
   228  			}
   229  			defer r.Close()
   230  			if _, err := io.Copy(w, r); err != nil {
   231  				return err
   232  			}
   233  		}
   234  	}
   235  
   236  	// TODO(hyangah): do we need to use aapt to create R.txt?
   237  	w, err = aarwcreate("R.txt")
   238  	if err != nil {
   239  		return err
   240  	}
   241  
   242  	w, err = aarwcreate("res/")
   243  	if err != nil {
   244  		return err
   245  	}
   246  
   247  	return aarw.Close()
   248  }
   249  
   250  const (
   251  	javacTargetVer = "1.7"
   252  	minAndroidAPI  = 15
   253  )
   254  
   255  func buildJar(w io.Writer, srcDir string) error {
   256  	var srcFiles []string
   257  	if buildN {
   258  		srcFiles = []string{"*.java"}
   259  	} else {
   260  		err := filepath.Walk(srcDir, func(path string, info os.FileInfo, err error) error {
   261  			if err != nil {
   262  				return err
   263  			}
   264  			if filepath.Ext(path) == ".java" {
   265  				srcFiles = append(srcFiles, filepath.Join(".", path[len(srcDir):]))
   266  			}
   267  			return nil
   268  		})
   269  		if err != nil {
   270  			return err
   271  		}
   272  	}
   273  
   274  	dst := filepath.Join(tmpdir, "javac-output")
   275  	if !buildN {
   276  		if err := os.MkdirAll(dst, 0700); err != nil {
   277  			return err
   278  		}
   279  	}
   280  
   281  	bClspath, err := bootClasspath()
   282  
   283  	if err != nil {
   284  		return err
   285  	}
   286  
   287  	args := []string{
   288  		"-d", dst,
   289  		"-source", javacTargetVer,
   290  		"-target", javacTargetVer,
   291  		"-bootclasspath", bClspath,
   292  	}
   293  	if bindClasspath != "" {
   294  		args = append(args, "-classpath", bindClasspath)
   295  	}
   296  
   297  	args = append(args, srcFiles...)
   298  
   299  	javac := exec.Command("javac", args...)
   300  	javac.Dir = srcDir
   301  	if err := runCmd(javac); err != nil {
   302  		return err
   303  	}
   304  
   305  	if buildX {
   306  		printcmd("jar c -C %s .", dst)
   307  	}
   308  	return writeJar(w, dst)
   309  }
   310  
   311  func writeJar(w io.Writer, dir string) error {
   312  	if buildN {
   313  		return nil
   314  	}
   315  	jarw := zip.NewWriter(w)
   316  	jarwcreate := func(name string) (io.Writer, error) {
   317  		if buildV {
   318  			fmt.Fprintf(os.Stderr, "jar: %s\n", name)
   319  		}
   320  		return jarw.Create(name)
   321  	}
   322  	f, err := jarwcreate("META-INF/MANIFEST.MF")
   323  	if err != nil {
   324  		return err
   325  	}
   326  	fmt.Fprintf(f, manifestHeader)
   327  
   328  	err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
   329  		if err != nil {
   330  			return err
   331  		}
   332  		if info.IsDir() {
   333  			return nil
   334  		}
   335  		out, err := jarwcreate(filepath.ToSlash(path[len(dir)+1:]))
   336  		if err != nil {
   337  			return err
   338  		}
   339  		in, err := os.Open(path)
   340  		if err != nil {
   341  			return err
   342  		}
   343  		defer in.Close()
   344  		_, err = io.Copy(out, in)
   345  		return err
   346  	})
   347  	if err != nil {
   348  		return err
   349  	}
   350  	return jarw.Close()
   351  }
   352  
   353  // androidAPIPath returns an android SDK platform directory under ANDROID_HOME.
   354  // If there are multiple platforms that satisfy the minimum version requirement
   355  // androidAPIPath returns the latest one among them.
   356  func androidAPIPath() (string, error) {
   357  	sdk := os.Getenv("ANDROID_HOME")
   358  	if sdk == "" {
   359  		return "", fmt.Errorf("ANDROID_HOME environment var is not set")
   360  	}
   361  	sdkDir, err := os.Open(filepath.Join(sdk, "platforms"))
   362  	if err != nil {
   363  		return "", fmt.Errorf("failed to find android SDK platform: %v", err)
   364  	}
   365  	defer sdkDir.Close()
   366  	fis, err := sdkDir.Readdir(-1)
   367  	if err != nil {
   368  		return "", fmt.Errorf("failed to find android SDK platform (min API level: %d): %v", minAndroidAPI, err)
   369  	}
   370  
   371  	var apiPath string
   372  	var apiVer int
   373  	for _, fi := range fis {
   374  		name := fi.Name()
   375  		if !fi.IsDir() || !strings.HasPrefix(name, "android-") {
   376  			continue
   377  		}
   378  		n, err := strconv.Atoi(name[len("android-"):])
   379  		if err != nil || n < minAndroidAPI {
   380  			continue
   381  		}
   382  		p := filepath.Join(sdkDir.Name(), name)
   383  		_, err = os.Stat(filepath.Join(p, "android.jar"))
   384  		if err == nil && apiVer < n {
   385  			apiPath = p
   386  			apiVer = n
   387  		}
   388  	}
   389  	if apiVer == 0 {
   390  		return "", fmt.Errorf("failed to find android SDK platform (min API level: %d) in %s",
   391  			minAndroidAPI, sdkDir.Name())
   392  	}
   393  	return apiPath, nil
   394  }