github.com/SkycoinProject/gomobile@v0.0.0-20190312151609-d3739f865fa6/cmd/gomobile/build_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  	"bytes"
     9  	"crypto/x509"
    10  	"encoding/base64"
    11  	"encoding/pem"
    12  	"encoding/xml"
    13  	"errors"
    14  	"fmt"
    15  	"go/build"
    16  	"io"
    17  	"io/ioutil"
    18  	"log"
    19  	"os"
    20  	"path"
    21  	"path/filepath"
    22  	"strings"
    23  
    24  	"golang.org/x/mobile/internal/binres"
    25  )
    26  
    27  func goAndroidBuild(pkg *build.Package, androidArchs []string) (map[string]bool, error) {
    28  	ndkRoot, err := ndkRoot()
    29  	if err != nil {
    30  		return nil, err
    31  	}
    32  	appName := path.Base(pkg.ImportPath)
    33  	libName := androidPkgName(appName)
    34  	manifestPath := filepath.Join(pkg.Dir, "AndroidManifest.xml")
    35  	manifestData, err := ioutil.ReadFile(manifestPath)
    36  	if err != nil {
    37  		if !os.IsNotExist(err) {
    38  			return nil, err
    39  		}
    40  
    41  		buf := new(bytes.Buffer)
    42  		buf.WriteString(`<?xml version="1.0" encoding="utf-8"?>`)
    43  		err := manifestTmpl.Execute(buf, manifestTmplData{
    44  			// TODO(crawshaw): a better package path.
    45  			JavaPkgPath: "org.golang.todo." + libName,
    46  			Name:        strings.Title(appName),
    47  			LibName:     libName,
    48  		})
    49  		if err != nil {
    50  			return nil, err
    51  		}
    52  		manifestData = buf.Bytes()
    53  		if buildV {
    54  			fmt.Fprintf(os.Stderr, "generated AndroidManifest.xml:\n%s\n", manifestData)
    55  		}
    56  	} else {
    57  		libName, err = manifestLibName(manifestData)
    58  		if err != nil {
    59  			return nil, fmt.Errorf("error parsing %s: %v", manifestPath, err)
    60  		}
    61  	}
    62  
    63  	libFiles := []string{}
    64  	nmpkgs := make(map[string]map[string]bool) // map: arch -> extractPkgs' output
    65  
    66  	for _, arch := range androidArchs {
    67  		env := androidEnv[arch]
    68  		toolchain := ndk.Toolchain(arch)
    69  		libPath := "lib/" + toolchain.abi + "/lib" + libName + ".so"
    70  		libAbsPath := filepath.Join(tmpdir, libPath)
    71  		if err := mkdir(filepath.Dir(libAbsPath)); err != nil {
    72  			return nil, err
    73  		}
    74  		err = goBuild(
    75  			pkg.ImportPath,
    76  			env,
    77  			"-buildmode=c-shared",
    78  			"-o", libAbsPath,
    79  		)
    80  		if err != nil {
    81  			return nil, err
    82  		}
    83  		nmpkgs[arch], err = extractPkgs(toolchain.Path(ndkRoot, "nm"), libAbsPath)
    84  		if err != nil {
    85  			return nil, err
    86  		}
    87  		libFiles = append(libFiles, libPath)
    88  	}
    89  
    90  	block, _ := pem.Decode([]byte(debugCert))
    91  	if block == nil {
    92  		return nil, errors.New("no debug cert")
    93  	}
    94  	privKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
    95  	if err != nil {
    96  		return nil, err
    97  	}
    98  
    99  	if buildO == "" {
   100  		buildO = androidPkgName(filepath.Base(pkg.Dir)) + ".apk"
   101  	}
   102  	if !strings.HasSuffix(buildO, ".apk") {
   103  		return nil, fmt.Errorf("output file name %q does not end in '.apk'", buildO)
   104  	}
   105  	var out io.Writer
   106  	if !buildN {
   107  		f, err := os.Create(buildO)
   108  		if err != nil {
   109  			return nil, err
   110  		}
   111  		defer func() {
   112  			if cerr := f.Close(); err == nil {
   113  				err = cerr
   114  			}
   115  		}()
   116  		out = f
   117  	}
   118  
   119  	var apkw *Writer
   120  	if !buildN {
   121  		apkw = NewWriter(out, privKey)
   122  	}
   123  	apkwCreate := func(name string) (io.Writer, error) {
   124  		if buildV {
   125  			fmt.Fprintf(os.Stderr, "apk: %s\n", name)
   126  		}
   127  		if buildN {
   128  			return ioutil.Discard, nil
   129  		}
   130  		return apkw.Create(name)
   131  	}
   132  	apkwWriteFile := func(dst, src string) error {
   133  		w, err := apkwCreate(dst)
   134  		if err != nil {
   135  			return err
   136  		}
   137  		if !buildN {
   138  			f, err := os.Open(src)
   139  			if err != nil {
   140  				return err
   141  			}
   142  			defer f.Close()
   143  			if _, err := io.Copy(w, f); err != nil {
   144  				return err
   145  			}
   146  		}
   147  		return nil
   148  	}
   149  
   150  	w, err := apkwCreate("classes.dex")
   151  	if err != nil {
   152  		return nil, err
   153  	}
   154  	dexData, err := base64.StdEncoding.DecodeString(dexStr)
   155  	if err != nil {
   156  		log.Fatalf("internal error bad dexStr: %v", err)
   157  	}
   158  	if _, err := w.Write(dexData); err != nil {
   159  		return nil, err
   160  	}
   161  
   162  	for _, libFile := range libFiles {
   163  		if err := apkwWriteFile(libFile, filepath.Join(tmpdir, libFile)); err != nil {
   164  			return nil, err
   165  		}
   166  	}
   167  
   168  	for _, arch := range androidArchs {
   169  		toolchain := ndk.Toolchain(arch)
   170  		if nmpkgs[arch]["golang.org/x/mobile/exp/audio/al"] {
   171  			dst := "lib/" + toolchain.abi + "/libopenal.so"
   172  			src := filepath.Join(gomobilepath, dst)
   173  			if _, err := os.Stat(src); err != nil {
   174  				return nil, errors.New("the Android requires the golang.org/x/mobile/exp/audio/al, but the OpenAL libraries was not found. Please run gomobile init with the -openal flag pointing to an OpenAL source directory.")
   175  			}
   176  			if err := apkwWriteFile(dst, src); err != nil {
   177  				return nil, err
   178  			}
   179  		}
   180  	}
   181  
   182  	// Add any assets.
   183  	var arsc struct {
   184  		iconPath string
   185  	}
   186  	assetsDir := filepath.Join(pkg.Dir, "assets")
   187  	assetsDirExists := true
   188  	fi, err := os.Stat(assetsDir)
   189  	if err != nil {
   190  		if os.IsNotExist(err) {
   191  			assetsDirExists = false
   192  		} else {
   193  			return nil, err
   194  		}
   195  	} else {
   196  		assetsDirExists = fi.IsDir()
   197  	}
   198  	if assetsDirExists {
   199  		// if assets is a symlink, follow the symlink.
   200  		assetsDir, err = filepath.EvalSymlinks(assetsDir)
   201  		if err != nil {
   202  			return nil, err
   203  		}
   204  		err = filepath.Walk(assetsDir, func(path string, info os.FileInfo, err error) error {
   205  			if err != nil {
   206  				return err
   207  			}
   208  			if name := filepath.Base(path); strings.HasPrefix(name, ".") {
   209  				// Do not include the hidden files.
   210  				return nil
   211  			}
   212  			if info.IsDir() {
   213  				return nil
   214  			}
   215  
   216  			if rel, err := filepath.Rel(assetsDir, path); rel == "icon.png" && err == nil {
   217  				arsc.iconPath = path
   218  				// TODO returning here does not write the assets/icon.png to the final assets output,
   219  				// making it unavailable via the assets API. Should the file be duplicated into assets
   220  				// or should assets API be able to retrieve files from the generated resource table?
   221  				return nil
   222  			}
   223  
   224  			name := "assets/" + path[len(assetsDir)+1:]
   225  			return apkwWriteFile(name, path)
   226  		})
   227  		if err != nil {
   228  			return nil, fmt.Errorf("asset %v", err)
   229  		}
   230  	}
   231  
   232  	bxml, err := binres.UnmarshalXML(bytes.NewReader(manifestData), arsc.iconPath != "")
   233  	if err != nil {
   234  		return nil, err
   235  	}
   236  
   237  	// generate resources.arsc identifying single xxxhdpi icon resource.
   238  	if arsc.iconPath != "" {
   239  		pkgname, err := bxml.RawValueByName("manifest", xml.Name{Local: "package"})
   240  		if err != nil {
   241  			return nil, err
   242  		}
   243  		tbl, name := binres.NewMipmapTable(pkgname)
   244  		if err := apkwWriteFile(name, arsc.iconPath); err != nil {
   245  			return nil, err
   246  		}
   247  		w, err := apkwCreate("resources.arsc")
   248  		if err != nil {
   249  			return nil, err
   250  		}
   251  		bin, err := tbl.MarshalBinary()
   252  		if err != nil {
   253  			return nil, err
   254  		}
   255  		if _, err := w.Write(bin); err != nil {
   256  			return nil, err
   257  		}
   258  	}
   259  
   260  	w, err = apkwCreate("AndroidManifest.xml")
   261  	if err != nil {
   262  		return nil, err
   263  	}
   264  	bin, err := bxml.MarshalBinary()
   265  	if err != nil {
   266  		return nil, err
   267  	}
   268  	if _, err := w.Write(bin); err != nil {
   269  		return nil, err
   270  	}
   271  
   272  	// TODO: add gdbserver to apk?
   273  
   274  	if !buildN {
   275  		if err := apkw.Close(); err != nil {
   276  			return nil, err
   277  		}
   278  	}
   279  
   280  	// TODO: return nmpkgs
   281  	return nmpkgs[androidArchs[0]], nil
   282  }
   283  
   284  // androidPkgName sanitizes the go package name to be acceptable as a android
   285  // package name part. The android package name convention is similar to the
   286  // java package name convention described in
   287  // https://docs.oracle.com/javase/specs/jls/se8/html/jls-6.html#jls-6.5.3.1
   288  // but not exactly same.
   289  func androidPkgName(name string) string {
   290  	var res []rune
   291  	for _, r := range name {
   292  		switch {
   293  		case 'a' <= r && r <= 'z', 'A' <= r && r <= 'Z', '0' <= r && r <= '9':
   294  			res = append(res, r)
   295  		default:
   296  			res = append(res, '_')
   297  		}
   298  	}
   299  	if len(res) == 0 || res[0] == '_' || ('0' <= res[0] && res[0] <= '9') {
   300  		// Android does not seem to allow the package part starting with _.
   301  		res = append([]rune{'g', 'o'}, res...)
   302  	}
   303  	s := string(res)
   304  	// Look for Java keywords that are not Go keywords, and avoid using
   305  	// them as a package name.
   306  	//
   307  	// This is not a problem for normal Go identifiers as we only expose
   308  	// exported symbols. The upper case first letter saves everything
   309  	// from accidentally matching except for the package name.
   310  	//
   311  	// Note that basic type names (like int) are not keywords in Go.
   312  	switch s {
   313  	case "abstract", "assert", "boolean", "byte", "catch", "char", "class",
   314  		"do", "double", "enum", "extends", "final", "finally", "float",
   315  		"implements", "instanceof", "int", "long", "native", "private",
   316  		"protected", "public", "short", "static", "strictfp", "super",
   317  		"synchronized", "this", "throw", "throws", "transient", "try",
   318  		"void", "volatile", "while":
   319  		s += "_"
   320  	}
   321  	return s
   322  }
   323  
   324  // A random uninteresting private key.
   325  // Must be consistent across builds so newer app versions can be installed.
   326  const debugCert = `
   327  -----BEGIN RSA PRIVATE KEY-----
   328  MIIEowIBAAKCAQEAy6ItnWZJ8DpX9R5FdWbS9Kr1U8Z7mKgqNByGU7No99JUnmyu
   329  NQ6Uy6Nj0Gz3o3c0BXESECblOC13WdzjsH1Pi7/L9QV8jXOXX8cvkG5SJAyj6hcO
   330  LOapjDiN89NXjXtyv206JWYvRtpexyVrmHJgRAw3fiFI+m4g4Qop1CxcIF/EgYh7
   331  rYrqh4wbCM1OGaCleQWaOCXxZGm+J5YNKQcWpjZRrDrb35IZmlT0bK46CXUKvCqK
   332  x7YXHgfhC8ZsXCtsScKJVHs7gEsNxz7A0XoibFw6DoxtjKzUCktnT0w3wxdY7OTj
   333  9AR8mobFlM9W3yirX8TtwekWhDNTYEu8dwwykwIDAQABAoIBAA2hjpIhvcNR9H9Z
   334  BmdEecydAQ0ZlT5zy1dvrWI++UDVmIp+Ve8BSd6T0mOqV61elmHi3sWsBN4M1Rdz
   335  3N38lW2SajG9q0fAvBpSOBHgAKmfGv3Ziz5gNmtHgeEXfZ3f7J95zVGhlHqWtY95
   336  JsmuplkHxFMyITN6WcMWrhQg4A3enKLhJLlaGLJf9PeBrvVxHR1/txrfENd2iJBH
   337  FmxVGILL09fIIktJvoScbzVOneeWXj5vJGzWVhB17DHBbANGvVPdD5f+k/s5aooh
   338  hWAy/yLKocr294C4J+gkO5h2zjjjSGcmVHfrhlXQoEPX+iW1TGoF8BMtl4Llc+jw
   339  lKWKfpECgYEA9C428Z6CvAn+KJ2yhbAtuRo41kkOVoiQPtlPeRYs91Pq4+NBlfKO
   340  2nWLkyavVrLx4YQeCeaEU2Xoieo9msfLZGTVxgRlztylOUR+zz2FzDBYGicuUD3s
   341  EqC0Wv7tiX6dumpWyOcVVLmR9aKlOUzA9xemzIsWUwL3PpyONhKSq7kCgYEA1X2F
   342  f2jKjoOVzglhtuX4/SP9GxS4gRf9rOQ1Q8DzZhyH2LZ6Dnb1uEQvGhiqJTU8CXxb
   343  7odI0fgyNXq425Nlxc1Tu0G38TtJhwrx7HWHuFcbI/QpRtDYLWil8Zr7Q3BT9rdh
   344  moo4m937hLMvqOG9pyIbyjOEPK2WBCtKW5yabqsCgYEAu9DkUBr1Qf+Jr+IEU9I8
   345  iRkDSMeusJ6gHMd32pJVCfRRQvIlG1oTyTMKpafmzBAd/rFpjYHynFdRcutqcShm
   346  aJUq3QG68U9EAvWNeIhA5tr0mUEz3WKTt4xGzYsyWES8u4tZr3QXMzD9dOuinJ1N
   347  +4EEumXtSPKKDG3M8Qh+KnkCgYBUEVSTYmF5EynXc2xOCGsuy5AsrNEmzJqxDUBI
   348  SN/P0uZPmTOhJIkIIZlmrlW5xye4GIde+1jajeC/nG7U0EsgRAV31J4pWQ5QJigz
   349  0+g419wxIUFryGuIHhBSfpP472+w1G+T2mAGSLh1fdYDq7jx6oWE7xpghn5vb9id
   350  EKLjdwKBgBtz9mzbzutIfAW0Y8F23T60nKvQ0gibE92rnUbjPnw8HjL3AZLU05N+
   351  cSL5bhq0N5XHK77sscxW9vXjG0LJMXmFZPp9F6aV6ejkMIXyJ/Yz/EqeaJFwilTq
   352  Mc6xR47qkdzu0dQ1aPm4XD7AWDtIvPo/GG2DKOucLBbQc2cOWtKS
   353  -----END RSA PRIVATE KEY-----
   354  `