| OLD | NEW |
| (Empty) |
| 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/pem" | |
| 11 "errors" | |
| 12 "fmt" | |
| 13 "go/build" | |
| 14 "io" | |
| 15 "io/ioutil" | |
| 16 "os" | |
| 17 "os/exec" | |
| 18 "path" | |
| 19 "path/filepath" | |
| 20 "strconv" | |
| 21 "strings" | |
| 22 ) | |
| 23 | |
| 24 var ctx = build.Default | |
| 25 var pkg *build.Package | |
| 26 var ndkccpath string | |
| 27 var tmpdir string | |
| 28 | |
| 29 var cmdBuild = &command{ | |
| 30 run: runBuild, | |
| 31 Name: "build", | |
| 32 Usage: "[package]", | |
| 33 Short: "compile android APK and/or iOS app", | |
| 34 Long: ` | |
| 35 Build compiles and encodes the app named by the import path. | |
| 36 | |
| 37 The named package must define a main function. | |
| 38 | |
| 39 If an AndroidManifest.xml is defined in the package directory, it is | |
| 40 added to the APK file. Otherwise, a default manifest is generated. | |
| 41 | |
| 42 If the package directory contains an assets subdirectory, its contents | |
| 43 are copied into the APK file. | |
| 44 | |
| 45 The -o flag specifies the output file name. If not specified, the | |
| 46 output file name depends on the package built. The output file must end | |
| 47 in '.apk'. | |
| 48 | |
| 49 The -v flag provides verbose output, including the list of packages built. | |
| 50 | |
| 51 These build flags are shared by the build, install, and test commands. | |
| 52 For documentation, see 'go help build': | |
| 53 -a | |
| 54 -i | |
| 55 -n | |
| 56 -x | |
| 57 -tags 'tag list' | |
| 58 `, | |
| 59 } | |
| 60 | |
| 61 // TODO: -mobile | |
| 62 | |
| 63 func runBuild(cmd *command) error { | |
| 64 cwd, err := os.Getwd() | |
| 65 if err != nil { | |
| 66 panic(err) | |
| 67 } | |
| 68 args := cmd.flag.Args() | |
| 69 | |
| 70 switch len(args) { | |
| 71 case 0: | |
| 72 pkg, err = ctx.ImportDir(cwd, build.ImportComment) | |
| 73 case 1: | |
| 74 pkg, err = ctx.Import(args[0], cwd, build.ImportComment) | |
| 75 default: | |
| 76 cmd.usage() | |
| 77 os.Exit(1) | |
| 78 } | |
| 79 if err != nil { | |
| 80 return err | |
| 81 } | |
| 82 | |
| 83 if pkg.Name != "main" { | |
| 84 // Not an app, don't build a final package. | |
| 85 return gobuild(pkg.ImportPath, "") | |
| 86 } | |
| 87 | |
| 88 // Building a program, make sure it is appropriate for mobile. | |
| 89 importsApp := false | |
| 90 for _, path := range pkg.Imports { | |
| 91 if path == "golang.org/x/mobile/app" { | |
| 92 importsApp = true | |
| 93 break | |
| 94 } | |
| 95 } | |
| 96 if !importsApp { | |
| 97 return fmt.Errorf(`%s does not import "golang.org/x/mobile/app"`
, pkg.ImportPath) | |
| 98 } | |
| 99 | |
| 100 if buildN { | |
| 101 tmpdir = "$WORK" | |
| 102 } else { | |
| 103 tmpdir, err = ioutil.TempDir("", "gobuildapk-work-") | |
| 104 if err != nil { | |
| 105 return err | |
| 106 } | |
| 107 } | |
| 108 defer removeAll(tmpdir) | |
| 109 if buildX { | |
| 110 fmt.Fprintln(os.Stderr, "WORK="+tmpdir) | |
| 111 } | |
| 112 | |
| 113 libName := path.Base(pkg.ImportPath) | |
| 114 manifestData, err := ioutil.ReadFile(filepath.Join(pkg.Dir, "AndroidMani
fest.xml")) | |
| 115 if err != nil { | |
| 116 if !os.IsNotExist(err) { | |
| 117 return err | |
| 118 } | |
| 119 buf := new(bytes.Buffer) | |
| 120 buf.WriteString(`<?xml version="1.0" encoding="utf-8"?>`) | |
| 121 err := manifestTmpl.Execute(buf, manifestTmplData{ | |
| 122 // TODO(crawshaw): a better package path. | |
| 123 JavaPkgPath: "org.golang.todo." + pkg.Name, | |
| 124 Name: strings.ToUpper(pkg.Name[:1]) + pkg.Name[1:
], | |
| 125 LibName: libName, | |
| 126 }) | |
| 127 if err != nil { | |
| 128 return err | |
| 129 } | |
| 130 manifestData = buf.Bytes() | |
| 131 if buildV { | |
| 132 fmt.Fprintf(os.Stderr, "generated AndroidManifest.xml:\n
%s\n", manifestData) | |
| 133 } | |
| 134 } else { | |
| 135 libName, err = manifestLibName(manifestData) | |
| 136 if err != nil { | |
| 137 return err | |
| 138 } | |
| 139 } | |
| 140 libPath := filepath.Join(tmpdir, "lib"+libName+".so") | |
| 141 | |
| 142 if err := gobuild(pkg.ImportPath, libPath); err != nil { | |
| 143 return err | |
| 144 } | |
| 145 block, _ := pem.Decode([]byte(debugCert)) | |
| 146 if block == nil { | |
| 147 return errors.New("no debug cert") | |
| 148 } | |
| 149 privKey, err := x509.ParsePKCS1PrivateKey(block.Bytes) | |
| 150 if err != nil { | |
| 151 return err | |
| 152 } | |
| 153 | |
| 154 if *buildO == "" { | |
| 155 *buildO = filepath.Base(pkg.Dir) + ".apk" | |
| 156 } | |
| 157 if !strings.HasSuffix(*buildO, ".apk") { | |
| 158 return fmt.Errorf("output file name %q does not end in '.apk'",
*buildO) | |
| 159 } | |
| 160 out, err := os.Create(*buildO) | |
| 161 if err != nil { | |
| 162 return err | |
| 163 } | |
| 164 | |
| 165 var apkw *Writer | |
| 166 if !buildN { | |
| 167 apkw = NewWriter(out, privKey) | |
| 168 } | |
| 169 apkwcreate := func(name string) (io.Writer, error) { | |
| 170 if buildV { | |
| 171 fmt.Fprintf(os.Stderr, "apk: %s\n", name) | |
| 172 } | |
| 173 if buildN { | |
| 174 return ioutil.Discard, nil | |
| 175 } | |
| 176 return apkw.Create(name) | |
| 177 } | |
| 178 | |
| 179 w, err := apkwcreate("AndroidManifest.xml") | |
| 180 if err != nil { | |
| 181 return err | |
| 182 } | |
| 183 if _, err := w.Write(manifestData); err != nil { | |
| 184 return err | |
| 185 } | |
| 186 | |
| 187 w, err = apkwcreate("lib/armeabi/lib" + libName + ".so") | |
| 188 if err != nil { | |
| 189 return err | |
| 190 } | |
| 191 if !buildN { | |
| 192 r, err := os.Open(libPath) | |
| 193 if err != nil { | |
| 194 return err | |
| 195 } | |
| 196 if _, err := io.Copy(w, r); err != nil { | |
| 197 return err | |
| 198 } | |
| 199 } | |
| 200 | |
| 201 // Add any assets. | |
| 202 assetsDir := filepath.Join(pkg.Dir, "assets") | |
| 203 assetsDirExists := true | |
| 204 fi, err := os.Stat(assetsDir) | |
| 205 if err != nil { | |
| 206 if os.IsNotExist(err) { | |
| 207 assetsDirExists = false | |
| 208 } else { | |
| 209 return err | |
| 210 } | |
| 211 } else { | |
| 212 assetsDirExists = fi.IsDir() | |
| 213 } | |
| 214 if assetsDirExists { | |
| 215 filepath.Walk(assetsDir, func(path string, info os.FileInfo, err
error) error { | |
| 216 if err != nil { | |
| 217 return err | |
| 218 } | |
| 219 if info.IsDir() { | |
| 220 return nil | |
| 221 } | |
| 222 name := "assets/" + path[len(assetsDir)+1:] | |
| 223 w, err := apkwcreate(name) | |
| 224 if err != nil { | |
| 225 return err | |
| 226 } | |
| 227 f, err := os.Open(path) | |
| 228 if err != nil { | |
| 229 return err | |
| 230 } | |
| 231 defer f.Close() | |
| 232 _, err = io.Copy(w, f) | |
| 233 return err | |
| 234 }) | |
| 235 } | |
| 236 | |
| 237 // TODO: add gdbserver to apk? | |
| 238 | |
| 239 if buildN { | |
| 240 return nil | |
| 241 } | |
| 242 return apkw.Close() | |
| 243 } | |
| 244 | |
| 245 var xout io.Writer = os.Stderr | |
| 246 | |
| 247 func printcmd(format string, args ...interface{}) { | |
| 248 cmd := fmt.Sprintf(format+"\n", args...) | |
| 249 if tmpdir != "" { | |
| 250 cmd = strings.Replace(cmd, tmpdir, "$WORK", -1) | |
| 251 } | |
| 252 if ndkccpath != "" { | |
| 253 cmd = strings.Replace(cmd, ndkccpath, "$NDKCCPATH", -1) | |
| 254 } | |
| 255 if env := os.Getenv("HOME"); env != "" { | |
| 256 cmd = strings.Replace(cmd, env, "$HOME", -1) | |
| 257 } | |
| 258 fmt.Fprint(xout, cmd) | |
| 259 } | |
| 260 | |
| 261 // "Build flags", used by multiple commands. | |
| 262 var ( | |
| 263 buildA bool // -a | |
| 264 buildI bool // -i | |
| 265 buildN bool // -n | |
| 266 buildV bool // -v | |
| 267 buildX bool // -x | |
| 268 buildO *string // -o | |
| 269 ) | |
| 270 | |
| 271 func addBuildFlags(cmd *command) { | |
| 272 cmd.flag.BoolVar(&buildA, "a", false, "") | |
| 273 cmd.flag.BoolVar(&buildI, "i", false, "") | |
| 274 cmd.flag.Var((*stringsFlag)(&ctx.BuildTags), "tags", "") | |
| 275 } | |
| 276 | |
| 277 func addBuildFlagsNVX(cmd *command) { | |
| 278 cmd.flag.BoolVar(&buildN, "n", false, "") | |
| 279 cmd.flag.BoolVar(&buildV, "v", false, "") | |
| 280 cmd.flag.BoolVar(&buildX, "x", false, "") | |
| 281 } | |
| 282 | |
| 283 // gobuild builds a package. | |
| 284 // If libPath is specified then it builds as a shared library. | |
| 285 func gobuild(src, libPath string) error { | |
| 286 version, err := goVersion() | |
| 287 if err != nil { | |
| 288 return err | |
| 289 } | |
| 290 | |
| 291 gopath := goEnv("GOPATH") | |
| 292 gomobilepath := "" | |
| 293 for _, p := range filepath.SplitList(gopath) { | |
| 294 gomobilepath = filepath.Join(p, "pkg", "gomobile") | |
| 295 if _, err = os.Stat(gomobilepath); err == nil { | |
| 296 break | |
| 297 } | |
| 298 } | |
| 299 if err != nil || gomobilepath == "" { | |
| 300 return errors.New("android toolchain not installed, run:\n\tgomo
bile init") | |
| 301 } | |
| 302 verpath := filepath.Join(gomobilepath, "version") | |
| 303 installedVersion, err := ioutil.ReadFile(verpath) | |
| 304 if err != nil { | |
| 305 return errors.New("android toolchain partially installed, run:\n
\tgomobile init") | |
| 306 } | |
| 307 if !bytes.Equal(installedVersion, version) { | |
| 308 return errors.New("android toolchain out of date, run:\n\tgomobi
le init") | |
| 309 } | |
| 310 | |
| 311 ndkccpath = filepath.Join(gomobilepath, "android-"+ndkVersion) | |
| 312 ndkccbin := filepath.Join(ndkccpath, "arm", "bin") | |
| 313 if buildX { | |
| 314 fmt.Fprintln(os.Stderr, "NDKCCPATH="+ndkccpath) | |
| 315 } | |
| 316 | |
| 317 gocmd := exec.Command( | |
| 318 `go`, | |
| 319 `build`, | |
| 320 `-tags=`+strconv.Quote(strings.Join(ctx.BuildTags, ",")), | |
| 321 `-toolexec=`+filepath.Join(ndkccbin, "toolexec")) | |
| 322 if buildV { | |
| 323 gocmd.Args = append(gocmd.Args, "-v") | |
| 324 } | |
| 325 if buildI { | |
| 326 gocmd.Args = append(gocmd.Args, "-i") | |
| 327 } | |
| 328 if buildX { | |
| 329 gocmd.Args = append(gocmd.Args, "-x") | |
| 330 } | |
| 331 if libPath == "" { | |
| 332 if *buildO != "" { | |
| 333 gocmd.Args = append(gocmd.Args, `-o`, *buildO) | |
| 334 } | |
| 335 } else { | |
| 336 gocmd.Args = append(gocmd.Args, | |
| 337 `-ldflags="-shared"`, | |
| 338 `-o`, libPath, | |
| 339 ) | |
| 340 } | |
| 341 | |
| 342 gocmd.Args = append(gocmd.Args, src) | |
| 343 | |
| 344 gocmd.Stdout = os.Stdout | |
| 345 gocmd.Stderr = os.Stderr | |
| 346 gocmd.Env = []string{ | |
| 347 `GOOS=android`, | |
| 348 `GOARCH=arm`, | |
| 349 `GOARM=7`, | |
| 350 `CGO_ENABLED=1`, | |
| 351 `CC=` + filepath.Join(ndkccbin, "arm-linux-androideabi-gcc"), | |
| 352 `CXX=` + filepath.Join(ndkccbin, "arm-linux-androideabi-g++"), | |
| 353 `GOGCCFLAGS="-fPIC -marm -pthread -fmessage-length=0"`, | |
| 354 `GOROOT=` + goEnv("GOROOT"), | |
| 355 `GOPATH=` + gopath, | |
| 356 `GOMOBILEPATH=` + ndkccbin, // for toolexec | |
| 357 } | |
| 358 if buildX { | |
| 359 printcmd("%s", strings.Join(gocmd.Env, " ")+" "+strings.Join(goc
md.Args, " ")) | |
| 360 } | |
| 361 if !buildN { | |
| 362 if err := gocmd.Run(); err != nil { | |
| 363 return err | |
| 364 } | |
| 365 } | |
| 366 return nil | |
| 367 } | |
| 368 | |
| 369 func init() { | |
| 370 buildO = cmdBuild.flag.String("o", "", "output file") | |
| 371 addBuildFlags(cmdBuild) | |
| 372 addBuildFlagsNVX(cmdBuild) | |
| 373 | |
| 374 addBuildFlags(cmdInstall) | |
| 375 addBuildFlagsNVX(cmdInstall) | |
| 376 | |
| 377 addBuildFlagsNVX(cmdInit) | |
| 378 | |
| 379 addBuildFlags(cmdBind) | |
| 380 addBuildFlagsNVX(cmdBind) | |
| 381 } | |
| 382 | |
| 383 // A random uninteresting private key. | |
| 384 // Must be consistent across builds so newer app versions can be installed. | |
| 385 const debugCert = ` | |
| 386 -----BEGIN RSA PRIVATE KEY----- | |
| 387 MIIEowIBAAKCAQEAy6ItnWZJ8DpX9R5FdWbS9Kr1U8Z7mKgqNByGU7No99JUnmyu | |
| 388 NQ6Uy6Nj0Gz3o3c0BXESECblOC13WdzjsH1Pi7/L9QV8jXOXX8cvkG5SJAyj6hcO | |
| 389 LOapjDiN89NXjXtyv206JWYvRtpexyVrmHJgRAw3fiFI+m4g4Qop1CxcIF/EgYh7 | |
| 390 rYrqh4wbCM1OGaCleQWaOCXxZGm+J5YNKQcWpjZRrDrb35IZmlT0bK46CXUKvCqK | |
| 391 x7YXHgfhC8ZsXCtsScKJVHs7gEsNxz7A0XoibFw6DoxtjKzUCktnT0w3wxdY7OTj | |
| 392 9AR8mobFlM9W3yirX8TtwekWhDNTYEu8dwwykwIDAQABAoIBAA2hjpIhvcNR9H9Z | |
| 393 BmdEecydAQ0ZlT5zy1dvrWI++UDVmIp+Ve8BSd6T0mOqV61elmHi3sWsBN4M1Rdz | |
| 394 3N38lW2SajG9q0fAvBpSOBHgAKmfGv3Ziz5gNmtHgeEXfZ3f7J95zVGhlHqWtY95 | |
| 395 JsmuplkHxFMyITN6WcMWrhQg4A3enKLhJLlaGLJf9PeBrvVxHR1/txrfENd2iJBH | |
| 396 FmxVGILL09fIIktJvoScbzVOneeWXj5vJGzWVhB17DHBbANGvVPdD5f+k/s5aooh | |
| 397 hWAy/yLKocr294C4J+gkO5h2zjjjSGcmVHfrhlXQoEPX+iW1TGoF8BMtl4Llc+jw | |
| 398 lKWKfpECgYEA9C428Z6CvAn+KJ2yhbAtuRo41kkOVoiQPtlPeRYs91Pq4+NBlfKO | |
| 399 2nWLkyavVrLx4YQeCeaEU2Xoieo9msfLZGTVxgRlztylOUR+zz2FzDBYGicuUD3s | |
| 400 EqC0Wv7tiX6dumpWyOcVVLmR9aKlOUzA9xemzIsWUwL3PpyONhKSq7kCgYEA1X2F | |
| 401 f2jKjoOVzglhtuX4/SP9GxS4gRf9rOQ1Q8DzZhyH2LZ6Dnb1uEQvGhiqJTU8CXxb | |
| 402 7odI0fgyNXq425Nlxc1Tu0G38TtJhwrx7HWHuFcbI/QpRtDYLWil8Zr7Q3BT9rdh | |
| 403 moo4m937hLMvqOG9pyIbyjOEPK2WBCtKW5yabqsCgYEAu9DkUBr1Qf+Jr+IEU9I8 | |
| 404 iRkDSMeusJ6gHMd32pJVCfRRQvIlG1oTyTMKpafmzBAd/rFpjYHynFdRcutqcShm | |
| 405 aJUq3QG68U9EAvWNeIhA5tr0mUEz3WKTt4xGzYsyWES8u4tZr3QXMzD9dOuinJ1N | |
| 406 +4EEumXtSPKKDG3M8Qh+KnkCgYBUEVSTYmF5EynXc2xOCGsuy5AsrNEmzJqxDUBI | |
| 407 SN/P0uZPmTOhJIkIIZlmrlW5xye4GIde+1jajeC/nG7U0EsgRAV31J4pWQ5QJigz | |
| 408 0+g419wxIUFryGuIHhBSfpP472+w1G+T2mAGSLh1fdYDq7jx6oWE7xpghn5vb9id | |
| 409 EKLjdwKBgBtz9mzbzutIfAW0Y8F23T60nKvQ0gibE92rnUbjPnw8HjL3AZLU05N+ | |
| 410 cSL5bhq0N5XHK77sscxW9vXjG0LJMXmFZPp9F6aV6ejkMIXyJ/Yz/EqeaJFwilTq | |
| 411 Mc6xR47qkdzu0dQ1aPm4XD7AWDtIvPo/GG2DKOucLBbQc2cOWtKS | |
| 412 -----END RSA PRIVATE KEY----- | |
| 413 ` | |
| OLD | NEW |