github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/mobile/cmd/gomobile/init.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 // TODO(crawshaw): android/{386,arm64} 8 9 import ( 10 "archive/tar" 11 "bytes" 12 "compress/gzip" 13 "crypto/sha256" 14 "encoding/hex" 15 "fmt" 16 "io" 17 "io/ioutil" 18 "net/http" 19 "os" 20 "os/exec" 21 "path" 22 "path/filepath" 23 "runtime" 24 "strings" 25 "time" 26 ) 27 28 // useStrippedNDK determines whether the init subcommand fetches the GCC 29 // toolchain from the original Android NDK, or from the stripped-down NDK 30 // hosted specifically for the gomobile tool. 31 // 32 // There is a significant size different (400MB compared to 30MB). 33 var useStrippedNDK = true 34 35 const ndkVersion = "ndk-r10e" 36 const openALVersion = "openal-soft-1.16.0.1" 37 38 var ( 39 goos = runtime.GOOS 40 goarch = runtime.GOARCH 41 ndkarch string 42 ) 43 44 func init() { 45 switch runtime.GOARCH { 46 case "amd64": 47 ndkarch = "x86_64" 48 case "386": 49 ndkarch = "x86" 50 default: 51 ndkarch = runtime.GOARCH 52 } 53 } 54 55 var cmdInit = &command{ 56 run: runInit, 57 Name: "init", 58 Usage: "[-u]", 59 Short: "install android compiler toolchain", 60 Long: ` 61 Init installs the Android C++ compiler toolchain and builds copies 62 of the Go standard library for mobile devices. 63 64 When first run, it downloads part of the Android NDK. 65 The toolchain is installed in $GOPATH/pkg/gomobile. 66 67 The -u option forces download and installation of the new toolchain 68 even when the toolchain exists. 69 `, 70 } 71 72 var initU bool // -u 73 74 func init() { 75 cmdInit.flag.BoolVar(&initU, "u", false, "force toolchain download") 76 } 77 78 func runInit(cmd *command) error { 79 gopaths := filepath.SplitList(goEnv("GOPATH")) 80 if len(gopaths) == 0 { 81 return fmt.Errorf("GOPATH is not set") 82 } 83 gomobilepath = filepath.Join(gopaths[0], "pkg/gomobile") 84 85 verpath := filepath.Join(gomobilepath, "version") 86 if buildX || buildN { 87 fmt.Fprintln(xout, "GOMOBILE="+gomobilepath) 88 } 89 removeGomobilepkg() 90 91 if err := mkdir(ndk.Root()); err != nil { 92 return err 93 } 94 95 if buildN { 96 tmpdir = filepath.Join(gomobilepath, "work") 97 } else { 98 var err error 99 tmpdir, err = ioutil.TempDir(gomobilepath, "work-") 100 if err != nil { 101 return err 102 } 103 } 104 if buildX || buildN { 105 fmt.Fprintln(xout, "WORK="+tmpdir) 106 } 107 defer func() { 108 if buildWork { 109 fmt.Printf("WORK=%s\n", tmpdir) 110 return 111 } 112 removeAll(tmpdir) 113 }() 114 115 if err := envInit(); err != nil { 116 return err 117 } 118 119 if err := fetchNDK(); err != nil { 120 return err 121 } 122 if err := fetchOpenAL(); err != nil { 123 return err 124 } 125 126 if runtime.GOOS == "darwin" { 127 // Install common x/mobile packages for local development. 128 // These are often slow to compile (due to cgo) and easy to forget. 129 // 130 // Limited to darwin for now as it is common for linux to 131 // not have GLES installed. 132 // 133 // TODO: consider testing GLES installation and suggesting it here 134 for _, pkg := range commonPkgs { 135 if err := installPkg(pkg, nil); err != nil { 136 return err 137 } 138 } 139 } 140 141 // Install standard libraries for cross compilers. 142 start := time.Now() 143 var androidArgs []string 144 if goVersion == go1_6 { 145 // Ideally this would be -buildmode=c-shared. 146 // https://golang.org/issue/13234. 147 androidArgs = []string{"-gcflags=-shared", "-ldflags=-shared"} 148 } 149 for _, env := range androidEnv { 150 if err := installStd(env, androidArgs...); err != nil { 151 return err 152 } 153 } 154 155 if err := installDarwin(); err != nil { 156 return err 157 } 158 159 if buildX || buildN { 160 printcmd("go version > %s", verpath) 161 } 162 if !buildN { 163 if err := ioutil.WriteFile(verpath, goVersionOut, 0644); err != nil { 164 return err 165 } 166 } 167 if buildV { 168 took := time.Since(start) / time.Second * time.Second 169 fmt.Fprintf(os.Stderr, "\nDone, build took %s.\n", took) 170 } 171 return nil 172 } 173 174 var commonPkgs = []string{ 175 "golang.org/x/mobile/gl", 176 "golang.org/x/mobile/app", 177 "golang.org/x/mobile/exp/app/debug", 178 } 179 180 func installDarwin() error { 181 if goos != "darwin" { 182 return nil // Only build iOS compilers on OS X. 183 } 184 if err := installStd(darwinArmEnv); err != nil { 185 return err 186 } 187 if err := installStd(darwinArm64Env); err != nil { 188 return err 189 } 190 // TODO(crawshaw): darwin/386 for the iOS simulator? 191 if err := installStd(darwinAmd64Env, "-tags=ios"); err != nil { 192 return err 193 } 194 return nil 195 } 196 197 func installStd(env []string, args ...string) error { 198 return installPkg("std", env, args...) 199 } 200 201 func installPkg(pkg string, env []string, args ...string) error { 202 tOS, tArch, pd := getenv(env, "GOOS"), getenv(env, "GOARCH"), pkgdir(env) 203 if tOS != "" && tArch != "" { 204 if buildV { 205 fmt.Fprintf(os.Stderr, "\n# Installing %s for %s/%s.\n", pkg, tOS, tArch) 206 } 207 args = append(args, "-pkgdir="+pd) 208 } else { 209 if buildV { 210 fmt.Fprintf(os.Stderr, "\n# Installing %s.\n", pkg) 211 } 212 } 213 214 // The -p flag is to speed up darwin/arm builds. 215 // Remove when golang.org/issue/10477 is resolved. 216 cmd := exec.Command("go", "install", fmt.Sprintf("-p=%d", runtime.NumCPU())) 217 cmd.Args = append(cmd.Args, args...) 218 if buildV { 219 cmd.Args = append(cmd.Args, "-v") 220 } 221 if buildX { 222 cmd.Args = append(cmd.Args, "-x") 223 } 224 if buildWork { 225 cmd.Args = append(cmd.Args, "-work") 226 } 227 cmd.Args = append(cmd.Args, pkg) 228 cmd.Env = append([]string{}, env...) 229 return runCmd(cmd) 230 } 231 232 func removeGomobilepkg() { 233 dir, err := os.Open(gomobilepath) 234 if err != nil { 235 return 236 } 237 names, err := dir.Readdirnames(-1) 238 if err != nil { 239 return 240 } 241 for _, name := range names { 242 if name == "dl" { 243 continue 244 } 245 removeAll(filepath.Join(gomobilepath, name)) 246 } 247 } 248 249 func move(dst, src string, names ...string) error { 250 for _, name := range names { 251 srcf := filepath.Join(src, name) 252 dstf := filepath.Join(dst, name) 253 if buildX || buildN { 254 printcmd("mv %s %s", srcf, dstf) 255 } 256 if buildN { 257 continue 258 } 259 if goos == "windows" { 260 // os.Rename fails if dstf already exists. 261 removeAll(dstf) 262 } 263 if err := os.Rename(srcf, dstf); err != nil { 264 return err 265 } 266 } 267 return nil 268 } 269 270 func mkdir(dir string) error { 271 if buildX || buildN { 272 printcmd("mkdir -p %s", dir) 273 } 274 if buildN { 275 return nil 276 } 277 return os.MkdirAll(dir, 0755) 278 } 279 280 func symlink(src, dst string) error { 281 if buildX || buildN { 282 printcmd("ln -s %s %s", src, dst) 283 } 284 if buildN { 285 return nil 286 } 287 if goos == "windows" { 288 return doCopyAll(dst, src) 289 } 290 return os.Symlink(src, dst) 291 } 292 293 func rm(name string) error { 294 if buildX || buildN { 295 printcmd("rm %s", name) 296 } 297 if buildN { 298 return nil 299 } 300 return os.Remove(name) 301 } 302 303 func fetchOpenAL() error { 304 url := "https://dl.google.com/go/mobile/gomobile-" + openALVersion + ".tar.gz" 305 archive, err := fetch(url) 306 if err != nil { 307 return err 308 } 309 if err := extract("openal", archive); err != nil { 310 return err 311 } 312 if goos == "windows" { 313 resetReadOnlyFlagAll(filepath.Join(tmpdir, "openal")) 314 } 315 ndkroot := ndk.Root() 316 src := filepath.Join(tmpdir, "openal/include/AL") 317 for arch := range androidEnv { 318 toolchain := ndk.Toolchain(arch) 319 dst := filepath.Join(ndkroot, toolchain.arch+"/sysroot/usr/include/AL") 320 if buildX || buildN { 321 printcmd("cp -r %s %s", src, dst) 322 } 323 if buildN { 324 continue 325 } 326 if err := doCopyAll(dst, src); err != nil { 327 return err 328 } 329 } 330 libDst := filepath.Join(ndkroot, "openal") 331 libSrc := filepath.Join(tmpdir, "openal") 332 if err := mkdir(libDst); err != nil { 333 return nil 334 } 335 if err := move(libDst, libSrc, "lib"); err != nil { 336 return err 337 } 338 return nil 339 } 340 341 func extract(dst, src string) error { 342 if buildX || buildN { 343 printcmd("tar xfz %s", src) 344 } 345 if buildN { 346 return nil 347 } 348 tf, err := os.Open(src) 349 if err != nil { 350 return err 351 } 352 defer tf.Close() 353 zr, err := gzip.NewReader(tf) 354 if err != nil { 355 return err 356 } 357 tr := tar.NewReader(zr) 358 for { 359 hdr, err := tr.Next() 360 if err == io.EOF { 361 break 362 } 363 if err != nil { 364 return err 365 } 366 dst := filepath.Join(tmpdir, dst+"/"+hdr.Name) 367 if hdr.Typeflag == tar.TypeSymlink { 368 if err := symlink(hdr.Linkname, dst); err != nil { 369 return err 370 } 371 continue 372 } 373 if err := os.MkdirAll(filepath.Dir(dst), 0755); err != nil { 374 return err 375 } 376 f, err := os.OpenFile(dst, os.O_CREATE|os.O_EXCL|os.O_WRONLY, os.FileMode(hdr.Mode)&0777) 377 if err != nil { 378 return err 379 } 380 if _, err := io.Copy(f, tr); err != nil { 381 return err 382 } 383 if err := f.Close(); err != nil { 384 return err 385 } 386 } 387 return nil 388 } 389 390 func fetchNDK() error { 391 if useStrippedNDK { 392 if err := fetchStrippedNDK(); err != nil { 393 return err 394 } 395 } else { 396 if err := fetchFullNDK(); err != nil { 397 return err 398 } 399 } 400 if goos == "windows" { 401 resetReadOnlyFlagAll(filepath.Join(tmpdir, "android-"+ndkVersion)) 402 } 403 404 for arch := range androidEnv { 405 toolchain := ndk.Toolchain(arch) 406 dst := filepath.Join(ndk.Root(), toolchain.arch) 407 dstSysroot := filepath.Join(dst, "sysroot") 408 if err := mkdir(dstSysroot); err != nil { 409 return err 410 } 411 412 srcSysroot := filepath.Join(tmpdir, fmt.Sprintf( 413 "android-%s/platforms/%s/arch-%s", ndkVersion, toolchain.platform, toolchain.arch)) 414 if err := move(dstSysroot, srcSysroot, "usr"); err != nil { 415 return err 416 } 417 418 ndkpath := filepath.Join(tmpdir, fmt.Sprintf( 419 "android-%s/toolchains/%s/prebuilt", ndkVersion, toolchain.gcc)) 420 if goos == "windows" && ndkarch == "x86" { 421 ndkpath = filepath.Join(ndkpath, "windows") 422 } else { 423 ndkpath = filepath.Join(ndkpath, goos+"-"+ndkarch) 424 } 425 if err := move(dst, ndkpath, "bin", "lib", "libexec"); err != nil { 426 return err 427 } 428 429 linkpath := filepath.Join(dst, toolchain.toolPrefix+"/bin") 430 if err := mkdir(linkpath); err != nil { 431 return err 432 } 433 434 for _, name := range []string{"ld", "as", "gcc", "g++"} { 435 if goos == "windows" { 436 name += ".exe" 437 } 438 if err := symlink(filepath.Join(dst, "bin", toolchain.toolPrefix+"-"+name), filepath.Join(linkpath, name)); err != nil { 439 return err 440 } 441 } 442 } 443 return nil 444 } 445 446 func fetchStrippedNDK() error { 447 url := "https://dl.google.com/go/mobile/gomobile-" + ndkVersion + "-" + goos + "-" + ndkarch + ".tar.gz" 448 archive, err := fetch(url) 449 if err != nil { 450 return err 451 } 452 return extract("", archive) 453 } 454 455 func fetchFullNDK() error { 456 url := "https://dl.google.com/android/ndk/android-" + ndkVersion + "-" + goos + "-" + ndkarch + "." 457 if goos == "windows" { 458 url += "exe" 459 } else { 460 url += "bin" 461 } 462 archive, err := fetch(url) 463 if err != nil { 464 return err 465 } 466 467 // The self-extracting ndk dist file for Windows terminates 468 // with an error (error code 2 - corrupted or incomplete file) 469 // but there are no details on what caused this. 470 // 471 // Strangely, if the file is launched from file browser or 472 // unzipped with 7z.exe no error is reported. 473 // 474 // In general we use the stripped NDK, so this code path 475 // is not used, and 7z.exe is not a normal dependency. 476 var inflate *exec.Cmd 477 if goos != "windows" { 478 // The downloaded archive is executed on linux and os x to unarchive. 479 // To do this execute permissions are needed. 480 os.Chmod(archive, 0755) 481 482 inflate = exec.Command(archive) 483 } else { 484 inflate = exec.Command("7z.exe", "x", archive) 485 } 486 inflate.Dir = tmpdir 487 return runCmd(inflate) 488 } 489 490 // fetch reads a URL into $GOPATH/pkg/gomobile/dl and returns the path 491 // to the downloaded file. Downloading is skipped if the file is 492 // already present. 493 func fetch(url string) (dst string, err error) { 494 if err := mkdir(filepath.Join(gomobilepath, "dl")); err != nil { 495 return "", err 496 } 497 name := path.Base(url) 498 dst = filepath.Join(gomobilepath, "dl", name) 499 500 // Use what's in the cache if force update is not required. 501 if !initU { 502 if buildX { 503 printcmd("stat %s", dst) 504 } 505 if _, err = os.Stat(dst); err == nil { 506 return dst, nil 507 } 508 } 509 if buildX { 510 printcmd("curl -o%s %s", dst, url) 511 } 512 if buildN { 513 return dst, nil 514 } 515 516 if buildV { 517 fmt.Fprintf(os.Stderr, "Downloading %s.\n", url) 518 } 519 520 f, err := ioutil.TempFile(tmpdir, "partial-"+name) 521 if err != nil { 522 return "", err 523 } 524 defer func() { 525 if err != nil { 526 f.Close() 527 os.Remove(f.Name()) 528 } 529 }() 530 hashw := sha256.New() 531 532 resp, err := http.Get(url) 533 if err != nil { 534 return "", err 535 } 536 if resp.StatusCode != http.StatusOK { 537 err = fmt.Errorf("error fetching %v, status: %v", url, resp.Status) 538 } else { 539 _, err = io.Copy(io.MultiWriter(hashw, f), resp.Body) 540 } 541 if err2 := resp.Body.Close(); err == nil { 542 err = err2 543 } 544 if err != nil { 545 return "", err 546 } 547 if err = f.Close(); err != nil { 548 return "", err 549 } 550 hash := hex.EncodeToString(hashw.Sum(nil)) 551 if fetchHashes[name] != hash { 552 return "", fmt.Errorf("sha256 for %q: %v, want %v", name, hash, fetchHashes[name]) 553 } 554 if err = os.Rename(f.Name(), dst); err != nil { 555 return "", err 556 } 557 return dst, nil 558 } 559 560 func doCopyAll(dst, src string) error { 561 return filepath.Walk(src, func(path string, info os.FileInfo, errin error) (err error) { 562 if errin != nil { 563 return errin 564 } 565 prefixLen := len(src) 566 if len(path) > prefixLen { 567 prefixLen++ // file separator 568 } 569 outpath := filepath.Join(dst, path[prefixLen:]) 570 if info.IsDir() { 571 return os.Mkdir(outpath, 0755) 572 } 573 in, err := os.Open(path) 574 if err != nil { 575 return err 576 } 577 defer in.Close() 578 out, err := os.OpenFile(outpath, os.O_CREATE|os.O_EXCL|os.O_WRONLY, info.Mode()) 579 if err != nil { 580 return err 581 } 582 defer func() { 583 if errc := out.Close(); err == nil { 584 err = errc 585 } 586 }() 587 _, err = io.Copy(out, in) 588 return err 589 }) 590 } 591 592 func removeAll(path string) error { 593 if buildX || buildN { 594 printcmd(`rm -r -f "%s"`, path) 595 } 596 if buildN { 597 return nil 598 } 599 600 // os.RemoveAll behaves differently in windows. 601 // http://golang.org/issues/9606 602 if goos == "windows" { 603 resetReadOnlyFlagAll(path) 604 } 605 606 return os.RemoveAll(path) 607 } 608 609 func resetReadOnlyFlagAll(path string) error { 610 fi, err := os.Stat(path) 611 if err != nil { 612 return err 613 } 614 if !fi.IsDir() { 615 return os.Chmod(path, 0666) 616 } 617 fd, err := os.Open(path) 618 if err != nil { 619 return err 620 } 621 defer fd.Close() 622 623 names, _ := fd.Readdirnames(-1) 624 for _, name := range names { 625 resetReadOnlyFlagAll(path + string(filepath.Separator) + name) 626 } 627 return nil 628 } 629 630 func goEnv(name string) string { 631 if val := os.Getenv(name); val != "" { 632 return val 633 } 634 val, err := exec.Command("go", "env", name).Output() 635 if err != nil { 636 panic(err) // the Go tool was tested to work earlier 637 } 638 return strings.TrimSpace(string(val)) 639 } 640 641 func runCmd(cmd *exec.Cmd) error { 642 if buildX || buildN { 643 dir := "" 644 if cmd.Dir != "" { 645 dir = "PWD=" + cmd.Dir + " " 646 } 647 env := strings.Join(cmd.Env, " ") 648 if env != "" { 649 env += " " 650 } 651 printcmd("%s%s%s", dir, env, strings.Join(cmd.Args, " ")) 652 } 653 654 buf := new(bytes.Buffer) 655 buf.WriteByte('\n') 656 if buildV { 657 cmd.Stdout = os.Stdout 658 cmd.Stderr = os.Stderr 659 } else { 660 cmd.Stdout = buf 661 cmd.Stderr = buf 662 } 663 664 if buildWork { 665 if goos == "windows" { 666 cmd.Env = append(cmd.Env, `TEMP=`+tmpdir) 667 cmd.Env = append(cmd.Env, `TMP=`+tmpdir) 668 } else { 669 cmd.Env = append(cmd.Env, `TMPDIR=`+tmpdir) 670 } 671 } 672 673 if !buildN { 674 cmd.Env = environ(cmd.Env) 675 if err := cmd.Run(); err != nil { 676 return fmt.Errorf("%s failed: %v%s", strings.Join(cmd.Args, " "), err, buf) 677 } 678 } 679 return nil 680 }