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