github.com/thommil/tge-mobile@v0.0.0-20190308225214-66a08abd51aa/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  
   159  	w, err = aarwcreate("classes.jar")
   160  	if err != nil {
   161  		return err
   162  	}
   163  	if err := buildJar(w, srcDir); err != nil {
   164  		return err
   165  	}
   166  
   167  	files := map[string]string{}
   168  	for _, pkg := range pkgs {
   169  		assetsDir := filepath.Join(pkg.Dir, "assets")
   170  		assetsDirExists := false
   171  		if fi, err := os.Stat(assetsDir); err == nil {
   172  			assetsDirExists = fi.IsDir()
   173  		} else if !os.IsNotExist(err) {
   174  			return err
   175  		}
   176  
   177  		if assetsDirExists {
   178  			err := filepath.Walk(
   179  				assetsDir, func(path string, info os.FileInfo, err error) error {
   180  					if err != nil {
   181  						return err
   182  					}
   183  					if info.IsDir() {
   184  						return nil
   185  					}
   186  					f, err := os.Open(path)
   187  					if err != nil {
   188  						return err
   189  					}
   190  					defer f.Close()
   191  					name := "assets/" + path[len(assetsDir)+1:]
   192  					if orig, exists := files[name]; exists {
   193  						return fmt.Errorf("package %s asset name conflict: %s already added from package %s",
   194  							pkg.ImportPath, name, orig)
   195  					}
   196  					files[name] = pkg.ImportPath
   197  					w, err := aarwcreate(name)
   198  					if err != nil {
   199  						return nil
   200  					}
   201  					_, err = io.Copy(w, f)
   202  					return err
   203  				})
   204  			if err != nil {
   205  				return err
   206  			}
   207  		}
   208  	}
   209  
   210  	for _, arch := range androidArchs {
   211  		toolchain := ndk.Toolchain(arch)
   212  		lib := toolchain.abi + "/libgojni.so"
   213  		w, err = aarwcreate("jni/" + lib)
   214  		if err != nil {
   215  			return err
   216  		}
   217  		if !buildN {
   218  			r, err := os.Open(filepath.Join(androidDir, "src/main/jniLibs/"+lib))
   219  			if err != nil {
   220  				return err
   221  			}
   222  			defer r.Close()
   223  			if _, err := io.Copy(w, r); err != nil {
   224  				return err
   225  			}
   226  		}
   227  	}
   228  
   229  	// TODO(hyangah): do we need to use aapt to create R.txt?
   230  	w, err = aarwcreate("R.txt")
   231  	if err != nil {
   232  		return err
   233  	}
   234  
   235  	w, err = aarwcreate("res/")
   236  	if err != nil {
   237  		return err
   238  	}
   239  
   240  	return aarw.Close()
   241  }
   242  
   243  const (
   244  	javacTargetVer = "1.7"
   245  	minAndroidAPI  = 15
   246  )
   247  
   248  func buildJar(w io.Writer, srcDir string) error {
   249  	var srcFiles []string
   250  	if buildN {
   251  		srcFiles = []string{"*.java"}
   252  	} else {
   253  		err := filepath.Walk(srcDir, func(path string, info os.FileInfo, err error) error {
   254  			if err != nil {
   255  				return err
   256  			}
   257  			if filepath.Ext(path) == ".java" {
   258  				srcFiles = append(srcFiles, filepath.Join(".", path[len(srcDir):]))
   259  			}
   260  			return nil
   261  		})
   262  		if err != nil {
   263  			return err
   264  		}
   265  	}
   266  
   267  	dst := filepath.Join(tmpdir, "javac-output")
   268  	if !buildN {
   269  		if err := os.MkdirAll(dst, 0700); err != nil {
   270  			return err
   271  		}
   272  	}
   273  
   274  	bClspath, err := bootClasspath()
   275  
   276  	if err != nil {
   277  		return err
   278  	}
   279  
   280  	args := []string{
   281  		"-d", dst,
   282  		"-source", javacTargetVer,
   283  		"-target", javacTargetVer,
   284  		"-bootclasspath", bClspath,
   285  	}
   286  	if bindClasspath != "" {
   287  		args = append(args, "-classpath", bindClasspath)
   288  	}
   289  
   290  	args = append(args, srcFiles...)
   291  
   292  	javac := exec.Command("javac", args...)
   293  	javac.Dir = srcDir
   294  	if err := runCmd(javac); err != nil {
   295  		return err
   296  	}
   297  
   298  	if buildX {
   299  		printcmd("jar c -C %s .", dst)
   300  	}
   301  	return writeJar(w, dst)
   302  }
   303  
   304  func writeJar(w io.Writer, dir string) error {
   305  	if buildN {
   306  		return nil
   307  	}
   308  	jarw := zip.NewWriter(w)
   309  	jarwcreate := func(name string) (io.Writer, error) {
   310  		if buildV {
   311  			fmt.Fprintf(os.Stderr, "jar: %s\n", name)
   312  		}
   313  		return jarw.Create(name)
   314  	}
   315  	f, err := jarwcreate("META-INF/MANIFEST.MF")
   316  	if err != nil {
   317  		return err
   318  	}
   319  	fmt.Fprintf(f, manifestHeader)
   320  
   321  	err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
   322  		if err != nil {
   323  			return err
   324  		}
   325  		if info.IsDir() {
   326  			return nil
   327  		}
   328  		out, err := jarwcreate(filepath.ToSlash(path[len(dir)+1:]))
   329  		if err != nil {
   330  			return err
   331  		}
   332  		in, err := os.Open(path)
   333  		if err != nil {
   334  			return err
   335  		}
   336  		defer in.Close()
   337  		_, err = io.Copy(out, in)
   338  		return err
   339  	})
   340  	if err != nil {
   341  		return err
   342  	}
   343  	return jarw.Close()
   344  }
   345  
   346  // androidAPIPath returns an android SDK platform directory under ANDROID_HOME.
   347  // If there are multiple platforms that satisfy the minimum version requirement
   348  // androidAPIPath returns the latest one among them.
   349  func androidAPIPath() (string, error) {
   350  	sdk := os.Getenv("ANDROID_HOME")
   351  	if sdk == "" {
   352  		return "", fmt.Errorf("ANDROID_HOME environment var is not set")
   353  	}
   354  	sdkDir, err := os.Open(filepath.Join(sdk, "platforms"))
   355  	if err != nil {
   356  		return "", fmt.Errorf("failed to find android SDK platform: %v", err)
   357  	}
   358  	defer sdkDir.Close()
   359  	fis, err := sdkDir.Readdir(-1)
   360  	if err != nil {
   361  		return "", fmt.Errorf("failed to find android SDK platform (min API level: %d): %v", minAndroidAPI, err)
   362  	}
   363  
   364  	var apiPath string
   365  	var apiVer int
   366  	for _, fi := range fis {
   367  		name := fi.Name()
   368  		if !fi.IsDir() || !strings.HasPrefix(name, "android-") {
   369  			continue
   370  		}
   371  		n, err := strconv.Atoi(name[len("android-"):])
   372  		if err != nil || n < minAndroidAPI {
   373  			continue
   374  		}
   375  		p := filepath.Join(sdkDir.Name(), name)
   376  		_, err = os.Stat(filepath.Join(p, "android.jar"))
   377  		if err == nil && apiVer < n {
   378  			apiPath = p
   379  			apiVer = n
   380  		}
   381  	}
   382  	if apiVer == 0 {
   383  		return "", fmt.Errorf("failed to find android SDK platform (min API level: %d) in %s",
   384  			minAndroidAPI, sdkDir.Name())
   385  	}
   386  	return apiPath, nil
   387  }