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