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 `