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