1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357
|
// 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"
"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 := os.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 io.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-----
`
|