github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/mobile/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(pkgs []*build.Package, androidArchs []string) 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  	var androidArgs []string
    26  	if goVersion == go1_6 {
    27  		// Ideally this would be -buildmode=c-shared.
    28  		// https://golang.org/issue/13234.
    29  		androidArgs = []string{"-gcflags=-shared", "-ldflags=-shared"}
    30  	}
    31  
    32  	paths := make([]string, len(pkgs))
    33  	for i, p := range pkgs {
    34  		paths[i] = p.ImportPath
    35  	}
    36  
    37  	androidDir := filepath.Join(tmpdir, "android")
    38  	mainFile := filepath.Join(tmpdir, "androidlib/main.go")
    39  
    40  	// Generate binding code and java source code only when processing the first package.
    41  	first := true
    42  	for _, arch := range androidArchs {
    43  		env := androidEnv[arch]
    44  		toolchain := ndk.Toolchain(arch)
    45  
    46  		if !first {
    47  			if err := goInstall(paths, env, androidArgs...); err != nil {
    48  				return err
    49  			}
    50  			err := goBuild(
    51  				mainFile,
    52  				env,
    53  				"-buildmode=c-shared",
    54  				"-o="+filepath.Join(androidDir, "src/main/jniLibs/"+toolchain.abi+"/libgojni.so"),
    55  			)
    56  			if err != nil {
    57  				return err
    58  			}
    59  
    60  			continue
    61  		}
    62  		first = false
    63  
    64  		typesPkgs, err := loadExportData(pkgs, env, androidArgs...)
    65  		if err != nil {
    66  			return fmt.Errorf("loadExportData failed %v", err)
    67  		}
    68  
    69  		binder, err := newBinder(typesPkgs)
    70  		if err != nil {
    71  			return err
    72  		}
    73  
    74  		for _, pkg := range typesPkgs {
    75  			if err := binder.GenGo(pkg, tmpdir); err != nil {
    76  				return err
    77  			}
    78  		}
    79  
    80  		err = writeFile(mainFile, func(w io.Writer) error {
    81  			return androidMainTmpl.Execute(w, binder.pkgs)
    82  		})
    83  		if err != nil {
    84  			return fmt.Errorf("failed to create the main package for android: %v", err)
    85  		}
    86  
    87  		err = goBuild(
    88  			mainFile,
    89  			env,
    90  			"-buildmode=c-shared",
    91  			"-o="+filepath.Join(androidDir, "src/main/jniLibs/"+toolchain.abi+"/libgojni.so"),
    92  		)
    93  		if err != nil {
    94  			return err
    95  		}
    96  
    97  		p, err := ctx.Import("golang.org/x/mobile/bind", cwd, build.ImportComment)
    98  		if err != nil {
    99  			return fmt.Errorf(`"golang.org/x/mobile/bind" is not found; run go get golang.org/x/mobile/bind`)
   100  		}
   101  		repo := filepath.Clean(filepath.Join(p.Dir, "..")) // golang.org/x/mobile directory.
   102  
   103  		for _, pkg := range binder.pkgs {
   104  			pkgpath := strings.Replace(bindJavaPkg, ".", "/", -1)
   105  			if bindJavaPkg == "" {
   106  				pkgpath = "go/" + pkg.Name()
   107  			}
   108  			if err := binder.GenJava(pkg, filepath.Join(androidDir, "src/main/java/"+pkgpath)); err != nil {
   109  				return err
   110  			}
   111  		}
   112  
   113  		dst := filepath.Join(androidDir, "src/main/java/go/LoadJNI.java")
   114  		genLoadJNI := func(w io.Writer) error {
   115  			_, err := io.WriteString(w, loadSrc)
   116  			return err
   117  		}
   118  		if err := writeFile(dst, genLoadJNI); err != nil {
   119  			return err
   120  		}
   121  
   122  		src := filepath.Join(repo, "bind/java/Seq.java")
   123  		dst = filepath.Join(androidDir, "src/main/java/go/Seq.java")
   124  		rm(dst)
   125  		if err := symlink(src, dst); err != nil {
   126  			return err
   127  		}
   128  	}
   129  
   130  	return buildAAR(androidDir, pkgs, androidArchs)
   131  }
   132  
   133  var loadSrc = `package go;
   134  
   135  import android.app.Application;
   136  import android.content.Context;
   137  
   138  import java.util.logging.Logger;
   139  
   140  public class LoadJNI {
   141  	private static Logger log = Logger.getLogger("GoLoadJNI");
   142  
   143  	public static final Object ctx;
   144  
   145  	static {
   146  		System.loadLibrary("gojni");
   147  
   148  		Object androidCtx = null;
   149  		try {
   150  			// TODO(hyangah): check proguard rule.
   151  			Application appl = (Application)Class.forName("android.app.AppGlobals").getMethod("getInitialApplication").invoke(null);
   152  			androidCtx = appl.getApplicationContext();
   153  		} catch (Exception e) {
   154                          log.warning("Global context not found: " + e);
   155  		} finally {
   156  			ctx = androidCtx;
   157  		}
   158  	}
   159  }
   160  `
   161  
   162  var androidMainTmpl = template.Must(template.New("android.go").Parse(`
   163  package main
   164  
   165  import (
   166  	_ "golang.org/x/mobile/bind/java"
   167  {{range .}}	_ "../go_{{.Name}}"
   168  {{end}}
   169  )
   170  
   171  func main() {}
   172  `))
   173  
   174  // AAR is the format for the binary distribution of an Android Library Project
   175  // and it is a ZIP archive with extension .aar.
   176  // http://tools.android.com/tech-docs/new-build-system/aar-format
   177  //
   178  // These entries are directly at the root of the archive.
   179  //
   180  //	AndroidManifest.xml (mandatory)
   181  // 	classes.jar (mandatory)
   182  //	assets/ (optional)
   183  //	jni/<abi>/libgojni.so
   184  //	R.txt (mandatory)
   185  //	res/ (mandatory)
   186  //	libs/*.jar (optional, not relevant)
   187  //	proguard.txt (optional)
   188  //	lint.jar (optional, not relevant)
   189  //	aidl (optional, not relevant)
   190  //
   191  // javac and jar commands are needed to build classes.jar.
   192  func buildAAR(androidDir string, pkgs []*build.Package, androidArchs []string) (err error) {
   193  	var out io.Writer = ioutil.Discard
   194  	if buildO == "" {
   195  		buildO = pkgs[0].Name + ".aar"
   196  	}
   197  	if !strings.HasSuffix(buildO, ".aar") {
   198  		return fmt.Errorf("output file name %q does not end in '.aar'", buildO)
   199  	}
   200  	if !buildN {
   201  		f, err := os.Create(buildO)
   202  		if err != nil {
   203  			return err
   204  		}
   205  		defer func() {
   206  			if cerr := f.Close(); err == nil {
   207  				err = cerr
   208  			}
   209  		}()
   210  		out = f
   211  	}
   212  
   213  	aarw := zip.NewWriter(out)
   214  	aarwcreate := func(name string) (io.Writer, error) {
   215  		if buildV {
   216  			fmt.Fprintf(os.Stderr, "aar: %s\n", name)
   217  		}
   218  		return aarw.Create(name)
   219  	}
   220  	w, err := aarwcreate("AndroidManifest.xml")
   221  	if err != nil {
   222  		return err
   223  	}
   224  	const manifestFmt = `<manifest xmlns:android="http://schemas.android.com/apk/res/android" package=%q>
   225  <uses-sdk android:minSdkVersion="%d"/></manifest>`
   226  	fmt.Fprintf(w, manifestFmt, "go."+pkgs[0].Name+".gojni", minAndroidAPI)
   227  
   228  	w, err = aarwcreate("proguard.txt")
   229  	if err != nil {
   230  		return err
   231  	}
   232  	fmt.Fprintln(w, `-keep class go.** { *; }`)
   233  
   234  	w, err = aarwcreate("classes.jar")
   235  	if err != nil {
   236  		return err
   237  	}
   238  	src := filepath.Join(androidDir, "src/main/java")
   239  	if err := buildJar(w, src); err != nil {
   240  		return err
   241  	}
   242  
   243  	files := map[string]string{}
   244  	for _, pkg := range pkgs {
   245  		assetsDir := filepath.Join(pkg.Dir, "assets")
   246  		assetsDirExists := false
   247  		if fi, err := os.Stat(assetsDir); err == nil {
   248  			assetsDirExists = fi.IsDir()
   249  		} else if !os.IsNotExist(err) {
   250  			return err
   251  		}
   252  
   253  		if assetsDirExists {
   254  			err := filepath.Walk(
   255  				assetsDir, func(path string, info os.FileInfo, err error) error {
   256  					if err != nil {
   257  						return err
   258  					}
   259  					if info.IsDir() {
   260  						return nil
   261  					}
   262  					f, err := os.Open(path)
   263  					if err != nil {
   264  						return err
   265  					}
   266  					defer f.Close()
   267  					name := "assets/" + path[len(assetsDir)+1:]
   268  					if orig, exists := files[name]; exists {
   269  						return fmt.Errorf("package %s asset name conflict: %s already added from package %s",
   270  							pkg.ImportPath, name, orig)
   271  					}
   272  					files[name] = pkg.ImportPath
   273  					w, err := aarwcreate(name)
   274  					if err != nil {
   275  						return nil
   276  					}
   277  					_, err = io.Copy(w, f)
   278  					return err
   279  				})
   280  			if err != nil {
   281  				return err
   282  			}
   283  		}
   284  	}
   285  
   286  	for _, arch := range androidArchs {
   287  		toolchain := ndk.Toolchain(arch)
   288  		lib := toolchain.abi + "/libgojni.so"
   289  		w, err = aarwcreate("jni/" + lib)
   290  		if err != nil {
   291  			return err
   292  		}
   293  		if !buildN {
   294  			r, err := os.Open(filepath.Join(androidDir, "src/main/jniLibs/"+lib))
   295  			if err != nil {
   296  				return err
   297  			}
   298  			defer r.Close()
   299  			if _, err := io.Copy(w, r); err != nil {
   300  				return err
   301  			}
   302  		}
   303  	}
   304  
   305  	// TODO(hyangah): do we need to use aapt to create R.txt?
   306  	w, err = aarwcreate("R.txt")
   307  	if err != nil {
   308  		return err
   309  	}
   310  
   311  	w, err = aarwcreate("res/")
   312  	if err != nil {
   313  		return err
   314  	}
   315  
   316  	return aarw.Close()
   317  }
   318  
   319  const (
   320  	javacTargetVer = "1.7"
   321  	minAndroidAPI  = 15
   322  )
   323  
   324  func buildJar(w io.Writer, srcDir string) error {
   325  	var srcFiles []string
   326  	if buildN {
   327  		srcFiles = []string{"*.java"}
   328  	} else {
   329  		err := filepath.Walk(srcDir, func(path string, info os.FileInfo, err error) error {
   330  			if err != nil {
   331  				return err
   332  			}
   333  			if filepath.Ext(path) == ".java" {
   334  				srcFiles = append(srcFiles, filepath.Join(".", path[len(srcDir):]))
   335  			}
   336  			return nil
   337  		})
   338  		if err != nil {
   339  			return err
   340  		}
   341  	}
   342  
   343  	dst := filepath.Join(tmpdir, "javac-output")
   344  	if !buildN {
   345  		if err := os.MkdirAll(dst, 0700); err != nil {
   346  			return err
   347  		}
   348  	}
   349  
   350  	apiPath, err := androidAPIPath()
   351  	if err != nil {
   352  		return err
   353  	}
   354  
   355  	args := []string{
   356  		"-d", dst,
   357  		"-source", javacTargetVer,
   358  		"-target", javacTargetVer,
   359  		"-bootclasspath", filepath.Join(apiPath, "android.jar"),
   360  	}
   361  	args = append(args, srcFiles...)
   362  
   363  	javac := exec.Command("javac", args...)
   364  	javac.Dir = srcDir
   365  	if err := runCmd(javac); err != nil {
   366  		return err
   367  	}
   368  
   369  	if buildX {
   370  		printcmd("jar c -C %s .", dst)
   371  	}
   372  	if buildN {
   373  		return nil
   374  	}
   375  	jarw := zip.NewWriter(w)
   376  	jarwcreate := func(name string) (io.Writer, error) {
   377  		if buildV {
   378  			fmt.Fprintf(os.Stderr, "jar: %s\n", name)
   379  		}
   380  		return jarw.Create(name)
   381  	}
   382  	f, err := jarwcreate("META-INF/MANIFEST.MF")
   383  	if err != nil {
   384  		return err
   385  	}
   386  	fmt.Fprintf(f, manifestHeader)
   387  
   388  	err = filepath.Walk(dst, func(path string, info os.FileInfo, err error) error {
   389  		if err != nil {
   390  			return err
   391  		}
   392  		if info.IsDir() {
   393  			return nil
   394  		}
   395  		out, err := jarwcreate(filepath.ToSlash(path[len(dst)+1:]))
   396  		if err != nil {
   397  			return err
   398  		}
   399  		in, err := os.Open(path)
   400  		if err != nil {
   401  			return err
   402  		}
   403  		defer in.Close()
   404  		_, err = io.Copy(out, in)
   405  		return err
   406  	})
   407  	if err != nil {
   408  		return err
   409  	}
   410  	return jarw.Close()
   411  }
   412  
   413  // androidAPIPath returns an android SDK platform directory under ANDROID_HOME.
   414  // If there are multiple platforms that satisfy the minimum version requirement
   415  // androidAPIPath returns the latest one among them.
   416  func androidAPIPath() (string, error) {
   417  	sdk := os.Getenv("ANDROID_HOME")
   418  	if sdk == "" {
   419  		return "", fmt.Errorf("ANDROID_HOME environment var is not set")
   420  	}
   421  	sdkDir, err := os.Open(filepath.Join(sdk, "platforms"))
   422  	if err != nil {
   423  		return "", fmt.Errorf("failed to find android SDK platform: %v", err)
   424  	}
   425  	defer sdkDir.Close()
   426  	fis, err := sdkDir.Readdir(-1)
   427  	if err != nil {
   428  		return "", fmt.Errorf("failed to find android SDK platform (min API level: %d): %v", minAndroidAPI, err)
   429  	}
   430  
   431  	var apiPath string
   432  	var apiVer int
   433  	for _, fi := range fis {
   434  		name := fi.Name()
   435  		if !fi.IsDir() || !strings.HasPrefix(name, "android-") {
   436  			continue
   437  		}
   438  		n, err := strconv.Atoi(name[len("android-"):])
   439  		if err != nil || n < minAndroidAPI {
   440  			continue
   441  		}
   442  		p := filepath.Join(sdkDir.Name(), name)
   443  		_, err = os.Stat(filepath.Join(p, "android.jar"))
   444  		if err == nil && apiVer < n {
   445  			apiPath = p
   446  			apiVer = n
   447  		}
   448  	}
   449  	if apiVer == 0 {
   450  		return "", fmt.Errorf("failed to find android SDK platform (min API level: %d) in %s",
   451  			minAndroidAPI, sdkDir.Name())
   452  	}
   453  	return apiPath, nil
   454  }