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