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 `