| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358 |
- // Copyright 2015 The Go Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style
- // license that can be found in the LICENSE file.
- package main
- import (
- "bytes"
- "crypto/x509"
- "encoding/base64"
- "encoding/pem"
- "encoding/xml"
- "errors"
- "fmt"
- "io"
- "io/ioutil"
- "log"
- "os"
- "path"
- "path/filepath"
- "strings"
- "golang.org/x/mobile/internal/binres"
- "golang.org/x/tools/go/packages"
- )
- func goAndroidBuild(pkg *packages.Package, targets []targetInfo) (map[string]bool, error) {
- ndkRoot, err := ndkRoot(targets...)
- if err != nil {
- return nil, err
- }
- appName := path.Base(pkg.PkgPath)
- libName := androidPkgName(appName)
- // TODO(hajimehoshi): This works only with Go tools that assume all source files are in one directory.
- // Fix this to work with other Go tools.
- dir := filepath.Dir(pkg.GoFiles[0])
- manifestPath := filepath.Join(dir, "AndroidManifest.xml")
- manifestData, err := ioutil.ReadFile(manifestPath)
- if err != nil {
- if !os.IsNotExist(err) {
- return nil, err
- }
- buf := new(bytes.Buffer)
- buf.WriteString(`<?xml version="1.0" encoding="utf-8"?>`)
- err := manifestTmpl.Execute(buf, manifestTmplData{
- // TODO(crawshaw): a better package path.
- JavaPkgPath: "org.golang.todo." + libName,
- Name: strings.Title(appName),
- LibName: libName,
- })
- if err != nil {
- return nil, err
- }
- manifestData = buf.Bytes()
- if buildV {
- fmt.Fprintf(os.Stderr, "generated AndroidManifest.xml:\n%s\n", manifestData)
- }
- } else {
- libName, err = manifestLibName(manifestData)
- if err != nil {
- return nil, fmt.Errorf("error parsing %s: %v", manifestPath, err)
- }
- }
- libFiles := []string{}
- nmpkgs := make(map[string]map[string]bool) // map: arch -> extractPkgs' output
- for _, t := range targets {
- toolchain := ndk.Toolchain(t.arch)
- libPath := "lib/" + toolchain.abi + "/lib" + libName + ".so"
- libAbsPath := filepath.Join(tmpdir, libPath)
- if err := mkdir(filepath.Dir(libAbsPath)); err != nil {
- return nil, err
- }
- err = goBuild(
- pkg.PkgPath,
- androidEnv[t.arch],
- "-buildmode=c-shared",
- "-o", libAbsPath,
- )
- if err != nil {
- return nil, err
- }
- nmpkgs[t.arch], err = extractPkgs(toolchain.Path(ndkRoot, "nm"), libAbsPath)
- if err != nil {
- return nil, err
- }
- libFiles = append(libFiles, libPath)
- }
- block, _ := pem.Decode([]byte(debugCert))
- if block == nil {
- return nil, errors.New("no debug cert")
- }
- privKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
- if err != nil {
- return nil, err
- }
- if buildO == "" {
- buildO = androidPkgName(path.Base(pkg.PkgPath)) + ".apk"
- }
- if !strings.HasSuffix(buildO, ".apk") {
- return nil, fmt.Errorf("output file name %q does not end in '.apk'", buildO)
- }
- var out io.Writer
- if !buildN {
- f, err := os.Create(buildO)
- if err != nil {
- return nil, err
- }
- defer func() {
- if cerr := f.Close(); err == nil {
- err = cerr
- }
- }()
- out = f
- }
- var apkw *Writer
- if !buildN {
- apkw = NewWriter(out, privKey)
- }
- apkwCreate := func(name string) (io.Writer, error) {
- if buildV {
- fmt.Fprintf(os.Stderr, "apk: %s\n", name)
- }
- if buildN {
- return ioutil.Discard, nil
- }
- return apkw.Create(name)
- }
- apkwWriteFile := func(dst, src string) error {
- w, err := apkwCreate(dst)
- if err != nil {
- return err
- }
- if !buildN {
- f, err := os.Open(src)
- if err != nil {
- return err
- }
- defer f.Close()
- if _, err := io.Copy(w, f); err != nil {
- return err
- }
- }
- return nil
- }
- w, err := apkwCreate("classes.dex")
- if err != nil {
- return nil, err
- }
- dexData, err := base64.StdEncoding.DecodeString(dexStr)
- if err != nil {
- log.Fatalf("internal error bad dexStr: %v", err)
- }
- if _, err := w.Write(dexData); err != nil {
- return nil, err
- }
- for _, libFile := range libFiles {
- if err := apkwWriteFile(libFile, filepath.Join(tmpdir, libFile)); err != nil {
- return nil, err
- }
- }
- for _, t := range targets {
- toolchain := ndk.Toolchain(t.arch)
- if nmpkgs[t.arch]["golang.org/x/mobile/exp/audio/al"] {
- dst := "lib/" + toolchain.abi + "/libopenal.so"
- src := filepath.Join(gomobilepath, dst)
- if _, err := os.Stat(src); err != nil {
- 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.")
- }
- if err := apkwWriteFile(dst, src); err != nil {
- return nil, err
- }
- }
- }
- // Add any assets.
- var arsc struct {
- iconPath string
- }
- assetsDir := filepath.Join(dir, "assets")
- assetsDirExists := true
- fi, err := os.Stat(assetsDir)
- if err != nil {
- if os.IsNotExist(err) {
- assetsDirExists = false
- } else {
- return nil, err
- }
- } else {
- assetsDirExists = fi.IsDir()
- }
- if assetsDirExists {
- // if assets is a symlink, follow the symlink.
- assetsDir, err = filepath.EvalSymlinks(assetsDir)
- if err != nil {
- return nil, err
- }
- err = filepath.Walk(assetsDir, func(path string, info os.FileInfo, err error) error {
- if err != nil {
- return err
- }
- if name := filepath.Base(path); strings.HasPrefix(name, ".") {
- // Do not include the hidden files.
- return nil
- }
- if info.IsDir() {
- return nil
- }
- if rel, err := filepath.Rel(assetsDir, path); rel == "icon.png" && err == nil {
- arsc.iconPath = path
- // TODO returning here does not write the assets/icon.png to the final assets output,
- // making it unavailable via the assets API. Should the file be duplicated into assets
- // or should assets API be able to retrieve files from the generated resource table?
- return nil
- }
- name := "assets/" + path[len(assetsDir)+1:]
- return apkwWriteFile(name, path)
- })
- if err != nil {
- return nil, fmt.Errorf("asset %v", err)
- }
- }
- bxml, err := binres.UnmarshalXML(bytes.NewReader(manifestData), arsc.iconPath != "")
- if err != nil {
- return nil, err
- }
- // generate resources.arsc identifying single xxxhdpi icon resource.
- if arsc.iconPath != "" {
- pkgname, err := bxml.RawValueByName("manifest", xml.Name{Local: "package"})
- if err != nil {
- return nil, err
- }
- tbl, name := binres.NewMipmapTable(pkgname)
- if err := apkwWriteFile(name, arsc.iconPath); err != nil {
- return nil, err
- }
- w, err := apkwCreate("resources.arsc")
- if err != nil {
- return nil, err
- }
- bin, err := tbl.MarshalBinary()
- if err != nil {
- return nil, err
- }
- if _, err := w.Write(bin); err != nil {
- return nil, err
- }
- }
- w, err = apkwCreate("AndroidManifest.xml")
- if err != nil {
- return nil, err
- }
- bin, err := bxml.MarshalBinary()
- if err != nil {
- return nil, err
- }
- if _, err := w.Write(bin); err != nil {
- return nil, err
- }
- // TODO: add gdbserver to apk?
- if !buildN {
- if err := apkw.Close(); err != nil {
- return nil, err
- }
- }
- // TODO: return nmpkgs
- return nmpkgs[targets[0].arch], nil
- }
- // androidPkgName sanitizes the go package name to be acceptable as a android
- // package name part. The android package name convention is similar to the
- // java package name convention described in
- // https://docs.oracle.com/javase/specs/jls/se8/html/jls-6.html#jls-6.5.3.1
- // but not exactly same.
- func androidPkgName(name string) string {
- var res []rune
- for _, r := range name {
- switch {
- case 'a' <= r && r <= 'z', 'A' <= r && r <= 'Z', '0' <= r && r <= '9':
- res = append(res, r)
- default:
- res = append(res, '_')
- }
- }
- if len(res) == 0 || res[0] == '_' || ('0' <= res[0] && res[0] <= '9') {
- // Android does not seem to allow the package part starting with _.
- res = append([]rune{'g', 'o'}, res...)
- }
- s := string(res)
- // Look for Java keywords that are not Go keywords, and avoid using
- // them as a package name.
- //
- // This is not a problem for normal Go identifiers as we only expose
- // exported symbols. The upper case first letter saves everything
- // from accidentally matching except for the package name.
- //
- // Note that basic type names (like int) are not keywords in Go.
- switch s {
- case "abstract", "assert", "boolean", "byte", "catch", "char", "class",
- "do", "double", "enum", "extends", "final", "finally", "float",
- "implements", "instanceof", "int", "long", "native", "private",
- "protected", "public", "short", "static", "strictfp", "super",
- "synchronized", "this", "throw", "throws", "transient", "try",
- "void", "volatile", "while":
- s += "_"
- }
- return s
- }
- // A random uninteresting private key.
- // Must be consistent across builds so newer app versions can be installed.
- const debugCert = `
- -----BEGIN RSA PRIVATE KEY-----
- MIIEowIBAAKCAQEAy6ItnWZJ8DpX9R5FdWbS9Kr1U8Z7mKgqNByGU7No99JUnmyu
- NQ6Uy6Nj0Gz3o3c0BXESECblOC13WdzjsH1Pi7/L9QV8jXOXX8cvkG5SJAyj6hcO
- LOapjDiN89NXjXtyv206JWYvRtpexyVrmHJgRAw3fiFI+m4g4Qop1CxcIF/EgYh7
- rYrqh4wbCM1OGaCleQWaOCXxZGm+J5YNKQcWpjZRrDrb35IZmlT0bK46CXUKvCqK
- x7YXHgfhC8ZsXCtsScKJVHs7gEsNxz7A0XoibFw6DoxtjKzUCktnT0w3wxdY7OTj
- 9AR8mobFlM9W3yirX8TtwekWhDNTYEu8dwwykwIDAQABAoIBAA2hjpIhvcNR9H9Z
- BmdEecydAQ0ZlT5zy1dvrWI++UDVmIp+Ve8BSd6T0mOqV61elmHi3sWsBN4M1Rdz
- 3N38lW2SajG9q0fAvBpSOBHgAKmfGv3Ziz5gNmtHgeEXfZ3f7J95zVGhlHqWtY95
- JsmuplkHxFMyITN6WcMWrhQg4A3enKLhJLlaGLJf9PeBrvVxHR1/txrfENd2iJBH
- FmxVGILL09fIIktJvoScbzVOneeWXj5vJGzWVhB17DHBbANGvVPdD5f+k/s5aooh
- hWAy/yLKocr294C4J+gkO5h2zjjjSGcmVHfrhlXQoEPX+iW1TGoF8BMtl4Llc+jw
- lKWKfpECgYEA9C428Z6CvAn+KJ2yhbAtuRo41kkOVoiQPtlPeRYs91Pq4+NBlfKO
- 2nWLkyavVrLx4YQeCeaEU2Xoieo9msfLZGTVxgRlztylOUR+zz2FzDBYGicuUD3s
- EqC0Wv7tiX6dumpWyOcVVLmR9aKlOUzA9xemzIsWUwL3PpyONhKSq7kCgYEA1X2F
- f2jKjoOVzglhtuX4/SP9GxS4gRf9rOQ1Q8DzZhyH2LZ6Dnb1uEQvGhiqJTU8CXxb
- 7odI0fgyNXq425Nlxc1Tu0G38TtJhwrx7HWHuFcbI/QpRtDYLWil8Zr7Q3BT9rdh
- moo4m937hLMvqOG9pyIbyjOEPK2WBCtKW5yabqsCgYEAu9DkUBr1Qf+Jr+IEU9I8
- iRkDSMeusJ6gHMd32pJVCfRRQvIlG1oTyTMKpafmzBAd/rFpjYHynFdRcutqcShm
- aJUq3QG68U9EAvWNeIhA5tr0mUEz3WKTt4xGzYsyWES8u4tZr3QXMzD9dOuinJ1N
- +4EEumXtSPKKDG3M8Qh+KnkCgYBUEVSTYmF5EynXc2xOCGsuy5AsrNEmzJqxDUBI
- SN/P0uZPmTOhJIkIIZlmrlW5xye4GIde+1jajeC/nG7U0EsgRAV31J4pWQ5QJigz
- 0+g419wxIUFryGuIHhBSfpP472+w1G+T2mAGSLh1fdYDq7jx6oWE7xpghn5vb9id
- EKLjdwKBgBtz9mzbzutIfAW0Y8F23T60nKvQ0gibE92rnUbjPnw8HjL3AZLU05N+
- cSL5bhq0N5XHK77sscxW9vXjG0LJMXmFZPp9F6aV6ejkMIXyJ/Yz/EqeaJFwilTq
- Mc6xR47qkdzu0dQ1aPm4XD7AWDtIvPo/GG2DKOucLBbQc2cOWtKS
- -----END RSA PRIVATE KEY-----
- `
|