gitlab.science.ru.nl/irma/gomobile.git@v0.0.0-20200320223732-da812b634d1f/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  	"io"
    11  	"io/ioutil"
    12  	"os"
    13  	"os/exec"
    14  	"path/filepath"
    15  	"strconv"
    16  	"strings"
    17  
    18  	"golang.org/x/tools/go/packages"
    19  )
    20  
    21  func goAndroidBind(gobind string, pkgs []*packages.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  	// Run gobind to generate the bindings
    27  	cmd := exec.Command(
    28  		gobind,
    29  		"-lang=go,java",
    30  		"-outdir="+tmpdir,
    31  	)
    32  	cmd.Env = append(cmd.Env, "GOOS=android")
    33  	cmd.Env = append(cmd.Env, "CGO_ENABLED=1")
    34  	if len(buildTags) > 0 {
    35  		cmd.Args = append(cmd.Args, "-tags="+strings.Join(buildTags, ","))
    36  	}
    37  	if bindJavaPkg != "" {
    38  		cmd.Args = append(cmd.Args, "-javapkg="+bindJavaPkg)
    39  	}
    40  	if bindClasspath != "" {
    41  		cmd.Args = append(cmd.Args, "-classpath="+bindClasspath)
    42  	}
    43  	if bindBootClasspath != "" {
    44  		cmd.Args = append(cmd.Args, "-bootclasspath="+bindBootClasspath)
    45  	}
    46  	for _, p := range pkgs {
    47  		cmd.Args = append(cmd.Args, p.PkgPath)
    48  	}
    49  	if err := runCmd(cmd); err != nil {
    50  		return err
    51  	}
    52  
    53  	androidDir := filepath.Join(tmpdir, "android")
    54  
    55  	// Generate binding code and java source code only when processing the first package.
    56  	for _, arch := range androidArchs {
    57  		if err := writeGoMod("android", arch); err != nil {
    58  			return err
    59  		}
    60  
    61  		env := androidEnv[arch]
    62  		// Add the generated packages to GOPATH for reverse bindings.
    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 := goBuildAt(
    68  			filepath.Join(tmpdir, "src"),
    69  			"./gobind",
    70  			env,
    71  			"-buildmode=c-shared",
    72  			"-o="+filepath.Join(androidDir, "src/main/jniLibs/"+toolchain.abi+"/libgojni.so"),
    73  		)
    74  		if err != nil {
    75  			return err
    76  		}
    77  	}
    78  
    79  	jsrc := filepath.Join(tmpdir, "java")
    80  	if err := buildAAR(jsrc, androidDir, pkgs, androidArchs); err != nil {
    81  		return err
    82  	}
    83  	return buildSrcJar(jsrc)
    84  }
    85  
    86  func buildSrcJar(src string) error {
    87  	var out io.Writer = ioutil.Discard
    88  	if !buildN {
    89  		ext := filepath.Ext(buildO)
    90  		f, err := os.Create(buildO[:len(buildO)-len(ext)] + "-sources.jar")
    91  		if err != nil {
    92  			return err
    93  		}
    94  		defer func() {
    95  			if cerr := f.Close(); err == nil {
    96  				err = cerr
    97  			}
    98  		}()
    99  		out = f
   100  	}
   101  
   102  	return writeJar(out, src)
   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(srcDir, androidDir string, pkgs []*packages.Package, androidArchs []string) (err error) {
   124  	var out io.Writer = ioutil.Discard
   125  	if buildO == "" {
   126  		buildO = pkgs[0].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  <uses-sdk android:minSdkVersion="%d"/></manifest>`
   157  	fmt.Fprintf(w, manifestFmt, "go."+pkgs[0].Name+".gojni", buildAndroidAPI)
   158  
   159  	w, err = aarwcreate("proguard.txt")
   160  	if err != nil {
   161  		return err
   162  	}
   163  	fmt.Fprintln(w, `-keep class go.** { *; }`)
   164  	if bindJavaPkg != "" {
   165  		fmt.Fprintln(w, `-keep class `+bindJavaPkg+`.** { *; }`)
   166  	} else {
   167  		for _, p := range pkgs {
   168  			fmt.Fprintln(w, `-keep class `+p.Name+`.** { *; }`)
   169  		}
   170  	}
   171  
   172  	w, err = aarwcreate("classes.jar")
   173  	if err != nil {
   174  		return err
   175  	}
   176  	if err := buildJar(w, srcDir); err != nil {
   177  		return err
   178  	}
   179  
   180  	files := map[string]string{}
   181  	for _, pkg := range pkgs {
   182  		// TODO(hajimehoshi): This works only with Go tools that assume all source files are in one directory.
   183  		// Fix this to work with other Go tools.
   184  		assetsDir := filepath.Join(filepath.Dir(pkg.GoFiles[0]), "assets")
   185  		assetsDirExists := false
   186  		if fi, err := os.Stat(assetsDir); err == nil {
   187  			assetsDirExists = fi.IsDir()
   188  		} else if !os.IsNotExist(err) {
   189  			return err
   190  		}
   191  
   192  		if assetsDirExists {
   193  			err := filepath.Walk(
   194  				assetsDir, func(path string, info os.FileInfo, err error) error {
   195  					if err != nil {
   196  						return err
   197  					}
   198  					if info.IsDir() {
   199  						return nil
   200  					}
   201  					f, err := os.Open(path)
   202  					if err != nil {
   203  						return err
   204  					}
   205  					defer f.Close()
   206  					name := "assets/" + path[len(assetsDir)+1:]
   207  					if orig, exists := files[name]; exists {
   208  						return fmt.Errorf("package %s asset name conflict: %s already added from package %s",
   209  							pkg.PkgPath, name, orig)
   210  					}
   211  					files[name] = pkg.PkgPath
   212  					w, err := aarwcreate(name)
   213  					if err != nil {
   214  						return nil
   215  					}
   216  					_, err = io.Copy(w, f)
   217  					return err
   218  				})
   219  			if err != nil {
   220  				return err
   221  			}
   222  		}
   223  	}
   224  
   225  	for _, arch := range androidArchs {
   226  		toolchain := ndk.Toolchain(arch)
   227  		lib := toolchain.abi + "/libgojni.so"
   228  		w, err = aarwcreate("jni/" + lib)
   229  		if err != nil {
   230  			return err
   231  		}
   232  		if !buildN {
   233  			r, err := os.Open(filepath.Join(androidDir, "src/main/jniLibs/"+lib))
   234  			if err != nil {
   235  				return err
   236  			}
   237  			defer r.Close()
   238  			if _, err := io.Copy(w, r); err != nil {
   239  				return err
   240  			}
   241  		}
   242  	}
   243  
   244  	// TODO(hyangah): do we need to use aapt to create R.txt?
   245  	w, err = aarwcreate("R.txt")
   246  	if err != nil {
   247  		return err
   248  	}
   249  
   250  	w, err = aarwcreate("res/")
   251  	if err != nil {
   252  		return err
   253  	}
   254  
   255  	return aarw.Close()
   256  }
   257  
   258  const (
   259  	javacTargetVer = "1.7"
   260  	minAndroidAPI  = 15
   261  )
   262  
   263  func buildJar(w io.Writer, srcDir string) error {
   264  	var srcFiles []string
   265  	if buildN {
   266  		srcFiles = []string{"*.java"}
   267  	} else {
   268  		err := filepath.Walk(srcDir, func(path string, info os.FileInfo, err error) error {
   269  			if err != nil {
   270  				return err
   271  			}
   272  			if filepath.Ext(path) == ".java" {
   273  				srcFiles = append(srcFiles, filepath.Join(".", path[len(srcDir):]))
   274  			}
   275  			return nil
   276  		})
   277  		if err != nil {
   278  			return err
   279  		}
   280  	}
   281  
   282  	dst := filepath.Join(tmpdir, "javac-output")
   283  	if !buildN {
   284  		if err := os.MkdirAll(dst, 0700); err != nil {
   285  			return err
   286  		}
   287  	}
   288  
   289  	bClspath, err := bootClasspath()
   290  
   291  	if err != nil {
   292  		return err
   293  	}
   294  
   295  	args := []string{
   296  		"-d", dst,
   297  		"-source", javacTargetVer,
   298  		"-target", javacTargetVer,
   299  		"-bootclasspath", bClspath,
   300  	}
   301  	if bindClasspath != "" {
   302  		args = append(args, "-classpath", bindClasspath)
   303  	}
   304  
   305  	args = append(args, srcFiles...)
   306  
   307  	javac := exec.Command("javac", args...)
   308  	javac.Dir = srcDir
   309  	if err := runCmd(javac); err != nil {
   310  		return err
   311  	}
   312  
   313  	if buildX {
   314  		printcmd("jar c -C %s .", dst)
   315  	}
   316  	return writeJar(w, dst)
   317  }
   318  
   319  func writeJar(w io.Writer, dir string) error {
   320  	if buildN {
   321  		return nil
   322  	}
   323  	jarw := zip.NewWriter(w)
   324  	jarwcreate := func(name string) (io.Writer, error) {
   325  		if buildV {
   326  			fmt.Fprintf(os.Stderr, "jar: %s\n", name)
   327  		}
   328  		return jarw.Create(name)
   329  	}
   330  	f, err := jarwcreate("META-INF/MANIFEST.MF")
   331  	if err != nil {
   332  		return err
   333  	}
   334  	fmt.Fprintf(f, manifestHeader)
   335  
   336  	err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
   337  		if err != nil {
   338  			return err
   339  		}
   340  		if info.IsDir() {
   341  			return nil
   342  		}
   343  		out, err := jarwcreate(filepath.ToSlash(path[len(dir)+1:]))
   344  		if err != nil {
   345  			return err
   346  		}
   347  		in, err := os.Open(path)
   348  		if err != nil {
   349  			return err
   350  		}
   351  		defer in.Close()
   352  		_, err = io.Copy(out, in)
   353  		return err
   354  	})
   355  	if err != nil {
   356  		return err
   357  	}
   358  	return jarw.Close()
   359  }
   360  
   361  // androidAPIPath returns an android SDK platform directory under ANDROID_HOME.
   362  // If there are multiple platforms that satisfy the minimum version requirement
   363  // androidAPIPath returns the latest one among them.
   364  func androidAPIPath() (string, error) {
   365  	sdk := os.Getenv("ANDROID_HOME")
   366  	if sdk == "" {
   367  		return "", fmt.Errorf("ANDROID_HOME environment var is not set")
   368  	}
   369  	sdkDir, err := os.Open(filepath.Join(sdk, "platforms"))
   370  	if err != nil {
   371  		return "", fmt.Errorf("failed to find android SDK platform: %v", err)
   372  	}
   373  	defer sdkDir.Close()
   374  	fis, err := sdkDir.Readdir(-1)
   375  	if err != nil {
   376  		return "", fmt.Errorf("failed to find android SDK platform (API level: %d): %v", buildAndroidAPI, err)
   377  	}
   378  
   379  	var apiPath string
   380  	var apiVer int
   381  	for _, fi := range fis {
   382  		name := fi.Name()
   383  		if !fi.IsDir() || !strings.HasPrefix(name, "android-") {
   384  			continue
   385  		}
   386  		n, err := strconv.Atoi(name[len("android-"):])
   387  		if err != nil || n < buildAndroidAPI {
   388  			continue
   389  		}
   390  		p := filepath.Join(sdkDir.Name(), name)
   391  		_, err = os.Stat(filepath.Join(p, "android.jar"))
   392  		if err == nil && apiVer < n {
   393  			apiPath = p
   394  			apiVer = n
   395  		}
   396  	}
   397  	if apiVer == 0 {
   398  		return "", fmt.Errorf("failed to find android SDK platform (API level: %d) in %s",
   399  			buildAndroidAPI, sdkDir.Name())
   400  	}
   401  	return apiPath, nil
   402  }