| 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 // TODO(crawshaw): build darwin/arm cross compiler on darwin/{386,amd64} | |
| 8 // TODO(crawshaw): android/{386,arm64} | |
| 9 | |
| 10 import ( | |
| 11 "archive/tar" | |
| 12 "bytes" | |
| 13 "compress/gzip" | |
| 14 "fmt" | |
| 15 "io" | |
| 16 "io/ioutil" | |
| 17 "net/http" | |
| 18 "os" | |
| 19 "os/exec" | |
| 20 "path/filepath" | |
| 21 "runtime" | |
| 22 "strings" | |
| 23 ) | |
| 24 | |
| 25 // useStrippedNDK determines whether the init subcommand fetches the GCC | |
| 26 // toolchain from the original Android NDK, or from the stripped-down NDK | |
| 27 // hosted specifically for the gomobile tool. | |
| 28 // | |
| 29 // There is a significant size different (400MB compared to 30MB). | |
| 30 var useStrippedNDK = goos == "linux" || goos == "darwin" | |
| 31 | |
| 32 const ndkVersion = "ndk-r10d" | |
| 33 | |
| 34 var ( | |
| 35 goos = runtime.GOOS | |
| 36 goarch = runtime.GOARCH | |
| 37 ndkarch string | |
| 38 ) | |
| 39 | |
| 40 func init() { | |
| 41 if runtime.GOARCH == "amd64" { | |
| 42 ndkarch = "x86_64" | |
| 43 } else { | |
| 44 ndkarch = runtime.GOARCH | |
| 45 } | |
| 46 } | |
| 47 | |
| 48 var cmdInit = &command{ | |
| 49 run: runInit, | |
| 50 Name: "init", | |
| 51 Short: "install android compiler toolchain", | |
| 52 Long: ` | |
| 53 Init downloads and installs the Android C++ compiler toolchain. | |
| 54 | |
| 55 The toolchain is installed in $GOPATH/pkg/gomobile. | |
| 56 If the Android C++ compiler toolchain already exists in the path, | |
| 57 it skips download and uses the existing toolchain. | |
| 58 | |
| 59 The -u option forces download and installation of the new toolchain | |
| 60 even when the toolchain exists. | |
| 61 `, | |
| 62 } | |
| 63 | |
| 64 var initU bool // -u | |
| 65 | |
| 66 func init() { | |
| 67 cmdInit.flag.BoolVar(&initU, "u", false, "force toolchain download") | |
| 68 } | |
| 69 | |
| 70 func runInit(cmd *command) error { | |
| 71 version, err := goVersion() | |
| 72 if err != nil { | |
| 73 return err | |
| 74 } | |
| 75 | |
| 76 gopaths := filepath.SplitList(goEnv("GOPATH")) | |
| 77 if len(gopaths) == 0 { | |
| 78 return fmt.Errorf("GOPATH is not set") | |
| 79 } | |
| 80 ndkccpath = filepath.Join(gopaths[0], "pkg", "gomobile", "android-"+ndkV
ersion) | |
| 81 ndkccdl := filepath.Join(ndkccpath, "downloaded") | |
| 82 verpath := filepath.Join(gopaths[0], "pkg", "gomobile", "version") | |
| 83 if buildX { | |
| 84 fmt.Fprintln(xout, "NDKCCPATH="+ndkccpath) | |
| 85 } | |
| 86 | |
| 87 needNDK := initU | |
| 88 if !needNDK { | |
| 89 if _, err := os.Stat(ndkccdl); err != nil { | |
| 90 needNDK = true | |
| 91 } | |
| 92 } | |
| 93 | |
| 94 if needNDK { | |
| 95 if err := removeAll(ndkccpath); err != nil && !os.IsExist(err) { | |
| 96 return err | |
| 97 } | |
| 98 if err := mkdir(ndkccpath); err != nil { | |
| 99 return err | |
| 100 } | |
| 101 } | |
| 102 | |
| 103 if buildN { | |
| 104 tmpdir = filepath.Join(ndkccpath, "work") | |
| 105 } else { | |
| 106 var err error | |
| 107 tmpdir, err = ioutil.TempDir(ndkccpath, "gomobile-init-") | |
| 108 if err != nil { | |
| 109 return err | |
| 110 } | |
| 111 } | |
| 112 if buildX { | |
| 113 fmt.Fprintln(xout, "WORK="+tmpdir) | |
| 114 } | |
| 115 defer removeAll(tmpdir) | |
| 116 | |
| 117 goroot := goEnv("GOROOT") | |
| 118 tmpGoroot := filepath.Join(tmpdir, "go") | |
| 119 if err := copyGoroot(tmpGoroot, goroot); err != nil { | |
| 120 return err | |
| 121 } | |
| 122 | |
| 123 if needNDK { | |
| 124 if err := fetchNDK(); err != nil { | |
| 125 return err | |
| 126 } | |
| 127 | |
| 128 if !buildN { | |
| 129 if err := ioutil.WriteFile(ndkccdl, []byte("done"), 0644
); err != nil { | |
| 130 return err | |
| 131 } | |
| 132 } | |
| 133 } | |
| 134 | |
| 135 dst := filepath.Join(ndkccpath, "arm") | |
| 136 | |
| 137 // TODO(crawshaw): make.bat on windows | |
| 138 ndkccbin := filepath.Join(dst, "bin") | |
| 139 envpath := os.Getenv("PATH") | |
| 140 if buildN { | |
| 141 envpath = "$PATH" | |
| 142 } | |
| 143 make := exec.Command(filepath.Join(tmpGoroot, "src", "make.bash"), "--no
-clean") | |
| 144 make.Dir = filepath.Join(tmpGoroot, "src") | |
| 145 make.Env = []string{ | |
| 146 `PATH=` + envpath, | |
| 147 `TMPDIR=` + tmpdir, | |
| 148 `HOME=` + os.Getenv("HOME"), // for default the go1.4 bootstrap | |
| 149 `GOOS=android`, | |
| 150 `GOARCH=arm`, | |
| 151 `GOARM=7`, | |
| 152 `CGO_ENABLED=1`, | |
| 153 `CC_FOR_TARGET=` + filepath.Join(ndkccbin, "arm-linux-androideab
i-gcc"), | |
| 154 `CXX_FOR_TARGET=` + filepath.Join(ndkccbin, "arm-linux-androidea
bi-g++"), | |
| 155 } | |
| 156 if v := goEnv("GOROOT_BOOTSTRAP"); v != "" { | |
| 157 make.Env = append(make.Env, `GOROOT_BOOTSTRAP=`+v) | |
| 158 } | |
| 159 if buildV { | |
| 160 fmt.Fprintf(os.Stderr, "building android/arm cross compiler\n") | |
| 161 make.Stdout = os.Stdout | |
| 162 make.Stderr = os.Stderr | |
| 163 } | |
| 164 if buildX { | |
| 165 printcmd("%s", strings.Join(make.Env, " ")+" "+strings.Join(make
.Args, " ")) | |
| 166 } | |
| 167 if !buildN { | |
| 168 if err := make.Run(); err != nil { | |
| 169 return err | |
| 170 } | |
| 171 } | |
| 172 | |
| 173 // Move the Go cross compiler toolchain into GOPATH. | |
| 174 gotoolsrc := filepath.Join(tmpGoroot, "pkg", "tool", goos+"_"+goarch) | |
| 175 if err := move(ndkccbin, gotoolsrc, "5a", "5l", "5g", "cgo", "nm", "pack
", "link"); err != nil { | |
| 176 return err | |
| 177 } | |
| 178 | |
| 179 // Build toolexec command. | |
| 180 toolexecSrc := filepath.Join(tmpdir, "toolexec.go") | |
| 181 if !buildN { | |
| 182 if err := ioutil.WriteFile(toolexecSrc, []byte(toolexec), 0644);
err != nil { | |
| 183 return err | |
| 184 } | |
| 185 } | |
| 186 make = exec.Command("go", "build", "-o", filepath.Join(ndkccbin, "toolex
ec"), toolexecSrc) | |
| 187 if buildV { | |
| 188 fmt.Fprintf(os.Stderr, "building gomobile toolexec\n") | |
| 189 make.Stdout = os.Stdout | |
| 190 make.Stderr = os.Stderr | |
| 191 } | |
| 192 if buildX { | |
| 193 printcmd("%s", strings.Join(make.Args, " ")) | |
| 194 } | |
| 195 if !buildN { | |
| 196 if err := make.Run(); err != nil { | |
| 197 return err | |
| 198 } | |
| 199 } | |
| 200 | |
| 201 // Move pre-compiled stdlib for android into GOROOT. This is | |
| 202 // the only time we modify the user's GOROOT. | |
| 203 cannotRemove := false | |
| 204 if err := removeAll(filepath.Join(goroot, "pkg", "android_arm")); err !=
nil { | |
| 205 cannotRemove = true | |
| 206 } | |
| 207 if err := move(filepath.Join(goroot, "pkg"), filepath.Join(tmpGoroot, "p
kg"), "android_arm"); err != nil { | |
| 208 // Move android_arm into a temp directory that outlives | |
| 209 // this process and give the user installation instructions. | |
| 210 dir, err := ioutil.TempDir("", "gomobile-") | |
| 211 if err != nil { | |
| 212 return err | |
| 213 } | |
| 214 if err := move(dir, filepath.Join(tmpGoroot, "pkg"), "android_ar
m"); err != nil { | |
| 215 return err | |
| 216 } | |
| 217 // TODO: modify instructions for windows. | |
| 218 remove := "" | |
| 219 if cannotRemove { | |
| 220 remove = "\trm -r -f %s/pkg/android_arm\n" | |
| 221 } | |
| 222 return fmt.Errorf( | |
| 223 `Cannot install android/arm in GOROOT. | |
| 224 Make GOROOT writable (possibly by becoming the super user, using sudo) and run: | |
| 225 %s mv %s %s`, | |
| 226 remove, | |
| 227 filepath.Join(dir, "android_arm"), | |
| 228 filepath.Join(goroot, "pkg"), | |
| 229 ) | |
| 230 } | |
| 231 | |
| 232 if buildX { | |
| 233 printcmd("go version > %s", verpath) | |
| 234 } | |
| 235 if !buildN { | |
| 236 if err := ioutil.WriteFile(verpath, version, 0644); err != nil { | |
| 237 return err | |
| 238 } | |
| 239 } | |
| 240 | |
| 241 return nil | |
| 242 } | |
| 243 | |
| 244 // toolexec is the source of a small program designed to be passed to | |
| 245 // the -toolexec flag of go build. | |
| 246 const toolexec = `package main | |
| 247 | |
| 248 import ( | |
| 249 "fmt" | |
| 250 "os" | |
| 251 "os/exec" | |
| 252 "path/filepath" | |
| 253 ) | |
| 254 | |
| 255 func main() { | |
| 256 args := append([]string{}, os.Args[1:]...) | |
| 257 args[0] = filepath.Join(os.Getenv("GOMOBILEPATH"), filepath.Base(args[0]
)) | |
| 258 cmd := exec.Command(args[0], args[1:]...) | |
| 259 cmd.Stdout = os.Stdout | |
| 260 cmd.Stderr = os.Stderr | |
| 261 if err := cmd.Run(); err != nil { | |
| 262 fmt.Fprintf(os.Stderr, "%v\n", err) | |
| 263 os.Exit(1) | |
| 264 } | |
| 265 } | |
| 266 ` | |
| 267 | |
| 268 func move(dst, src string, names ...string) error { | |
| 269 for _, name := range names { | |
| 270 srcf := filepath.Join(src, name) | |
| 271 dstf := filepath.Join(dst, name) | |
| 272 if buildX { | |
| 273 printcmd("mv %s %s", srcf, dstf) | |
| 274 } | |
| 275 if buildN { | |
| 276 continue | |
| 277 } | |
| 278 if err := os.Rename(srcf, dstf); err != nil { | |
| 279 return err | |
| 280 } | |
| 281 } | |
| 282 return nil | |
| 283 } | |
| 284 | |
| 285 func mkdir(dir string) error { | |
| 286 if buildX { | |
| 287 printcmd("mkdir -p %s", dir) | |
| 288 } | |
| 289 if buildN { | |
| 290 return nil | |
| 291 } | |
| 292 return os.MkdirAll(dir, 0755) | |
| 293 } | |
| 294 | |
| 295 func symlink(src, dst string) error { | |
| 296 if buildX { | |
| 297 printcmd("ln -s %s %s", src, dst) | |
| 298 } | |
| 299 if buildN { | |
| 300 return nil | |
| 301 } | |
| 302 return os.Symlink(src, dst) | |
| 303 } | |
| 304 | |
| 305 func rm(name string) error { | |
| 306 if buildX { | |
| 307 printcmd("rm %s", name) | |
| 308 } | |
| 309 if buildN { | |
| 310 return nil | |
| 311 } | |
| 312 return os.Remove(name) | |
| 313 } | |
| 314 | |
| 315 func goVersion() ([]byte, error) { | |
| 316 if err := exec.Command("which", "go").Run(); err != nil { | |
| 317 return nil, fmt.Errorf(`no Go tool on $PATH`) | |
| 318 } | |
| 319 buildHelp, err := exec.Command("go", "help", "build").Output() | |
| 320 if err != nil { | |
| 321 return nil, fmt.Errorf("bad Go tool: %v", err) | |
| 322 } | |
| 323 if !bytes.Contains(buildHelp, []byte("-toolexec")) { | |
| 324 return nil, fmt.Errorf("installed Go tool does not support -tool
exec") | |
| 325 } | |
| 326 return exec.Command("go", "version").Output() | |
| 327 } | |
| 328 | |
| 329 func fetchNDK() error { | |
| 330 if useStrippedNDK { | |
| 331 if err := fetchStrippedNDK(); err != nil { | |
| 332 return err | |
| 333 } | |
| 334 } else { | |
| 335 ndkName := "android-" + ndkVersion + "-" + goos + "-" + ndkarch
+ "." | |
| 336 if goos == "windows" { | |
| 337 ndkName += "exe" | |
| 338 } else { | |
| 339 ndkName += "bin" | |
| 340 } | |
| 341 url := "https://dl.google.com/android/ndk/" + ndkName | |
| 342 if err := fetch(filepath.Join(tmpdir, ndkName), url); err != nil
{ | |
| 343 return err | |
| 344 } | |
| 345 | |
| 346 inflate := exec.Command(filepath.Join(tmpdir, ndkName)) | |
| 347 inflate.Dir = tmpdir | |
| 348 if buildX { | |
| 349 printcmd("%s", inflate.Args[0]) | |
| 350 } | |
| 351 if !buildN { | |
| 352 out, err := inflate.CombinedOutput() | |
| 353 if err != nil { | |
| 354 if buildV { | |
| 355 os.Stderr.Write(out) | |
| 356 } | |
| 357 return err | |
| 358 } | |
| 359 } | |
| 360 } | |
| 361 | |
| 362 dst := filepath.Join(ndkccpath, "arm") | |
| 363 dstSysroot := filepath.Join(dst, "sysroot", "usr") | |
| 364 if err := mkdir(dstSysroot); err != nil { | |
| 365 return err | |
| 366 } | |
| 367 | |
| 368 srcSysroot := filepath.Join(tmpdir, "android-ndk-r10d", "platforms", "an
droid-15", "arch-arm", "usr") | |
| 369 if err := move(dstSysroot, srcSysroot, "include", "lib"); err != nil { | |
| 370 return err | |
| 371 } | |
| 372 | |
| 373 ndkpath := filepath.Join(tmpdir, "android-ndk-r10d", "toolchains", "arm-
linux-androideabi-4.8", "prebuilt", goos+"-"+ndkarch) | |
| 374 if err := move(dst, ndkpath, "bin", "lib", "libexec"); err != nil { | |
| 375 return err | |
| 376 } | |
| 377 | |
| 378 linkpath := filepath.Join(dst, "arm-linux-androideabi", "bin") | |
| 379 if err := mkdir(linkpath); err != nil { | |
| 380 return err | |
| 381 } | |
| 382 for _, name := range []string{"ld", "as", "gcc", "g++"} { | |
| 383 if err := symlink(filepath.Join(dst, "bin", "arm-linux-androidea
bi-"+name), filepath.Join(linkpath, name)); err != nil { | |
| 384 return err | |
| 385 } | |
| 386 } | |
| 387 return nil | |
| 388 } | |
| 389 | |
| 390 func fetchStrippedNDK() error { | |
| 391 name := "gomobile-ndk-r10d-" + goos + "-" + ndkarch + ".tar.gz" | |
| 392 url := "https://dl.google.com/go/mobile/" + name | |
| 393 if err := fetch(filepath.Join(tmpdir, name), url); err != nil { | |
| 394 return err | |
| 395 } | |
| 396 if buildX { | |
| 397 printcmd("tar xfz %s", name) | |
| 398 } | |
| 399 if buildN { | |
| 400 return nil | |
| 401 } | |
| 402 | |
| 403 tf, err := os.Open(filepath.Join(tmpdir, name)) | |
| 404 if err != nil { | |
| 405 return err | |
| 406 } | |
| 407 defer tf.Close() | |
| 408 zr, err := gzip.NewReader(tf) | |
| 409 if err != nil { | |
| 410 return err | |
| 411 } | |
| 412 tr := tar.NewReader(zr) | |
| 413 for { | |
| 414 hdr, err := tr.Next() | |
| 415 if err == io.EOF { | |
| 416 break | |
| 417 } | |
| 418 if err != nil { | |
| 419 return err | |
| 420 } | |
| 421 dst := filepath.Join(tmpdir, hdr.Name) | |
| 422 if hdr.Typeflag == tar.TypeSymlink { | |
| 423 if err := symlink(hdr.Linkname, dst); err != nil { | |
| 424 return err | |
| 425 } | |
| 426 continue | |
| 427 } | |
| 428 if err := os.MkdirAll(filepath.Dir(dst), 0755); err != nil { | |
| 429 return err | |
| 430 } | |
| 431 f, err := os.OpenFile(dst, os.O_CREATE|os.O_EXCL|os.O_WRONLY, os
.FileMode(hdr.Mode)&0777) | |
| 432 if err != nil { | |
| 433 return err | |
| 434 } | |
| 435 if _, err := io.Copy(f, tr); err != nil { | |
| 436 return err | |
| 437 } | |
| 438 if err := f.Close(); err != nil { | |
| 439 return err | |
| 440 } | |
| 441 } | |
| 442 return nil | |
| 443 } | |
| 444 | |
| 445 func fetch(dst, url string) error { | |
| 446 if buildV { | |
| 447 fmt.Fprintf(os.Stderr, "fetching %s\n", url) | |
| 448 } | |
| 449 if buildX { | |
| 450 printcmd("curl -o%s %s", dst, url) | |
| 451 } | |
| 452 if buildN { | |
| 453 return nil | |
| 454 } | |
| 455 | |
| 456 f, err := os.OpenFile(dst, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0755) | |
| 457 if err != nil { | |
| 458 return err | |
| 459 } | |
| 460 | |
| 461 resp, err := http.Get(url) | |
| 462 if err != nil { | |
| 463 return err | |
| 464 } | |
| 465 _, err = io.Copy(f, resp.Body) | |
| 466 err2 := resp.Body.Close() | |
| 467 err3 := f.Close() | |
| 468 if err != nil { | |
| 469 return err | |
| 470 } | |
| 471 if err2 != nil { | |
| 472 return err2 | |
| 473 } | |
| 474 return err3 | |
| 475 } | |
| 476 | |
| 477 // copyGoroot copies GOROOT from src to dst. | |
| 478 // | |
| 479 // It skips the pkg directory, which is not necessary for make.bash, | |
| 480 // and symlinks .git to avoid a 70MB copy. | |
| 481 func copyGoroot(dst, src string) error { | |
| 482 if err := mkdir(filepath.Join(dst, "pkg")); err != nil { | |
| 483 return err | |
| 484 } | |
| 485 for _, dir := range []string{"include", "lib", "src"} { | |
| 486 if err := copyAll(filepath.Join(dst, dir), filepath.Join(src, di
r)); err != nil { | |
| 487 return err | |
| 488 } | |
| 489 } | |
| 490 return symlink(filepath.Join(src, ".git"), filepath.Join(dst, ".git")) | |
| 491 } | |
| 492 | |
| 493 func copyAll(dst, src string) error { | |
| 494 if buildX { | |
| 495 printcmd("cp -a %s %s", src, dst) | |
| 496 } | |
| 497 if buildN { | |
| 498 return nil | |
| 499 } | |
| 500 return filepath.Walk(src, func(path string, info os.FileInfo, errin erro
r) (err error) { | |
| 501 if errin != nil { | |
| 502 return errin | |
| 503 } | |
| 504 prefixLen := len(src) | |
| 505 if len(path) > prefixLen { | |
| 506 prefixLen++ // file separator | |
| 507 } | |
| 508 outpath := filepath.Join(dst, path[prefixLen:]) | |
| 509 if info.IsDir() { | |
| 510 return os.Mkdir(outpath, 0755) | |
| 511 } | |
| 512 in, err := os.Open(path) | |
| 513 if err != nil { | |
| 514 return err | |
| 515 } | |
| 516 defer in.Close() | |
| 517 out, err := os.OpenFile(outpath, os.O_CREATE|os.O_EXCL|os.O_WRON
LY, info.Mode()) | |
| 518 if err != nil { | |
| 519 return err | |
| 520 } | |
| 521 defer func() { | |
| 522 if errc := out.Close(); err == nil { | |
| 523 err = errc | |
| 524 } | |
| 525 }() | |
| 526 _, err = io.Copy(out, in) | |
| 527 return err | |
| 528 }) | |
| 529 } | |
| 530 | |
| 531 func removeAll(path string) error { | |
| 532 if buildX { | |
| 533 printcmd("rm -r -f %q", path) | |
| 534 } | |
| 535 if buildN { | |
| 536 return nil | |
| 537 } | |
| 538 return os.RemoveAll(path) | |
| 539 } | |
| 540 | |
| 541 func goEnv(name string) string { | |
| 542 if val := os.Getenv(name); val != "" { | |
| 543 return val | |
| 544 } | |
| 545 val, err := exec.Command("go", "env", name).Output() | |
| 546 if err != nil { | |
| 547 panic(err) // the Go tool was tested to work earlier | |
| 548 } | |
| 549 return strings.TrimSpace(string(val)) | |
| 550 } | |
| OLD | NEW |