2
0
mirror of synced 2025-02-24 23:38:22 +00:00
mobile/cmd/gomobile/init.go
Elias Naur efd7eed289 mobile/cmd/gomobile: upgrade to NDK r12b and add nm
gomobile build uses the nm tool which was missing from the NDK r12
upgrade. I missed it because gomobile bind works without it.

Make release.go include nm and upgrade to NDK r12b to make avoid
name clashes with the already released, but inadequate, NDK r12
files.

Finally, update the cmd/gomobile tests to match the new clang
reality.

Fixes golang/go#16268

Change-Id: Ic0cbf75785baace1fe6e88c8dc72d83ce2e13b35
Reviewed-on: https://go-review.googlesource.com/24724
Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
2016-07-11 14:34:23 +00:00

687 lines
15 KiB
Go

// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"archive/tar"
"bytes"
"compress/gzip"
"crypto/sha256"
"encoding/hex"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"os/exec"
"path"
"path/filepath"
"runtime"
"strings"
"time"
)
// useStrippedNDK determines whether the init subcommand fetches the clang
// toolchain from the original Android NDK, or from the stripped-down NDK
// hosted specifically for the gomobile tool.
//
// There is a significant size different (400MB compared to 30MB).
var useStrippedNDK = true
const ndkVersion = "ndk-r12b"
const openALVersion = "openal-soft-1.16.0.1-" + ndkVersion
var (
goos = runtime.GOOS
goarch = runtime.GOARCH
ndkarch string
)
func init() {
switch runtime.GOARCH {
case "amd64":
ndkarch = "x86_64"
case "386":
ndkarch = "x86"
default:
ndkarch = runtime.GOARCH
}
}
var cmdInit = &command{
run: runInit,
Name: "init",
Usage: "[-u]",
Short: "install android compiler toolchain",
Long: `
Init installs the Android C++ compiler toolchain and builds copies
of the Go standard library for mobile devices.
When first run, it downloads part of the Android NDK.
The toolchain is installed in $GOPATH/pkg/gomobile.
The -u option forces download and installation of the new toolchain
even when the toolchain exists.
`,
}
var initU bool // -u
func init() {
cmdInit.flag.BoolVar(&initU, "u", false, "force toolchain download")
}
func runInit(cmd *command) error {
gopaths := filepath.SplitList(goEnv("GOPATH"))
if len(gopaths) == 0 {
return fmt.Errorf("GOPATH is not set")
}
gomobilepath = filepath.Join(gopaths[0], "pkg/gomobile")
verpath := filepath.Join(gomobilepath, "version")
if buildX || buildN {
fmt.Fprintln(xout, "GOMOBILE="+gomobilepath)
}
removeGomobilepkg()
if err := mkdir(ndk.Root()); err != nil {
return err
}
if buildN {
tmpdir = filepath.Join(gomobilepath, "work")
} else {
var err error
tmpdir, err = ioutil.TempDir(gomobilepath, "work-")
if err != nil {
return err
}
}
if buildX || buildN {
fmt.Fprintln(xout, "WORK="+tmpdir)
}
defer func() {
if buildWork {
fmt.Printf("WORK=%s\n", tmpdir)
return
}
removeAll(tmpdir)
}()
if err := envInit(); err != nil {
return err
}
if err := fetchNDK(); err != nil {
return err
}
if err := fetchOpenAL(); err != nil {
return err
}
if runtime.GOOS == "darwin" {
// Install common x/mobile packages for local development.
// These are often slow to compile (due to cgo) and easy to forget.
//
// Limited to darwin for now as it is common for linux to
// not have GLES installed.
//
// TODO: consider testing GLES installation and suggesting it here
for _, pkg := range commonPkgs {
if err := installPkg(pkg, nil); err != nil {
return err
}
}
}
// Install standard libraries for cross compilers.
start := time.Now()
var androidArgs []string
if goVersion == go1_6 {
// Ideally this would be -buildmode=c-shared.
// https://golang.org/issue/13234.
androidArgs = []string{"-gcflags=-shared", "-ldflags=-shared"}
}
for _, env := range androidEnv {
if err := installStd(env, androidArgs...); err != nil {
return err
}
}
if err := installDarwin(); err != nil {
return err
}
if buildX || buildN {
printcmd("go version > %s", verpath)
}
if !buildN {
if err := ioutil.WriteFile(verpath, goVersionOut, 0644); err != nil {
return err
}
}
if buildV {
took := time.Since(start) / time.Second * time.Second
fmt.Fprintf(os.Stderr, "\nDone, build took %s.\n", took)
}
return nil
}
var commonPkgs = []string{
"golang.org/x/mobile/gl",
"golang.org/x/mobile/app",
"golang.org/x/mobile/exp/app/debug",
}
func installDarwin() error {
if goos != "darwin" {
return nil // Only build iOS compilers on OS X.
}
if err := installStd(darwinArmEnv); err != nil {
return err
}
if err := installStd(darwinArm64Env); err != nil {
return err
}
// TODO(crawshaw): darwin/386 for the iOS simulator?
if err := installStd(darwinAmd64Env, "-tags=ios"); err != nil {
return err
}
return nil
}
func installStd(env []string, args ...string) error {
return installPkg("std", env, args...)
}
func installPkg(pkg string, env []string, args ...string) error {
tOS, tArch, pd := getenv(env, "GOOS"), getenv(env, "GOARCH"), pkgdir(env)
if tOS != "" && tArch != "" {
if buildV {
fmt.Fprintf(os.Stderr, "\n# Installing %s for %s/%s.\n", pkg, tOS, tArch)
}
args = append(args, "-pkgdir="+pd)
} else {
if buildV {
fmt.Fprintf(os.Stderr, "\n# Installing %s.\n", pkg)
}
}
// The -p flag is to speed up darwin/arm builds.
// Remove when golang.org/issue/10477 is resolved.
cmd := exec.Command("go", "install", fmt.Sprintf("-p=%d", runtime.NumCPU()))
cmd.Args = append(cmd.Args, args...)
if buildV {
cmd.Args = append(cmd.Args, "-v")
}
if buildX {
cmd.Args = append(cmd.Args, "-x")
}
if buildWork {
cmd.Args = append(cmd.Args, "-work")
}
cmd.Args = append(cmd.Args, pkg)
cmd.Env = append([]string{}, env...)
return runCmd(cmd)
}
func removeGomobilepkg() {
dir, err := os.Open(gomobilepath)
if err != nil {
return
}
names, err := dir.Readdirnames(-1)
if err != nil {
return
}
for _, name := range names {
if name == "dl" {
continue
}
removeAll(filepath.Join(gomobilepath, name))
}
}
func move(dst, src string, names ...string) error {
for _, name := range names {
srcf := filepath.Join(src, name)
dstf := filepath.Join(dst, name)
if buildX || buildN {
printcmd("mv %s %s", srcf, dstf)
}
if buildN {
continue
}
if goos == "windows" {
// os.Rename fails if dstf already exists.
removeAll(dstf)
}
if err := os.Rename(srcf, dstf); err != nil {
return err
}
}
return nil
}
func mkdir(dir string) error {
if buildX || buildN {
printcmd("mkdir -p %s", dir)
}
if buildN {
return nil
}
return os.MkdirAll(dir, 0755)
}
func symlink(src, dst string) error {
if buildX || buildN {
printcmd("ln -s %s %s", src, dst)
}
if buildN {
return nil
}
if goos == "windows" {
return doCopyAll(dst, src)
}
return os.Symlink(src, dst)
}
func rm(name string) error {
if buildX || buildN {
printcmd("rm %s", name)
}
if buildN {
return nil
}
return os.Remove(name)
}
func fetchOpenAL() error {
url := "https://dl.google.com/go/mobile/gomobile-" + openALVersion + ".tar.gz"
archive, err := fetch(url)
if err != nil {
return err
}
if err := extract("openal", archive); err != nil {
return err
}
if goos == "windows" {
resetReadOnlyFlagAll(filepath.Join(tmpdir, "openal"))
}
ndkroot := ndk.Root()
src := filepath.Join(tmpdir, "openal/include/AL")
for arch := range androidEnv {
toolchain := ndk.Toolchain(arch)
dst := filepath.Join(ndkroot, toolchain.arch+"/sysroot/usr/include/AL")
if buildX || buildN {
printcmd("cp -r %s %s", src, dst)
}
if buildN {
continue
}
if err := doCopyAll(dst, src); err != nil {
return err
}
}
libDst := filepath.Join(ndkroot, "openal")
libSrc := filepath.Join(tmpdir, "openal")
if err := mkdir(libDst); err != nil {
return nil
}
if err := move(libDst, libSrc, "lib"); err != nil {
return err
}
return nil
}
func extract(dst, src string) error {
if buildX || buildN {
printcmd("tar xfz %s", src)
}
if buildN {
return nil
}
tf, err := os.Open(src)
if err != nil {
return err
}
defer tf.Close()
zr, err := gzip.NewReader(tf)
if err != nil {
return err
}
tr := tar.NewReader(zr)
for {
hdr, err := tr.Next()
if err == io.EOF {
break
}
if err != nil {
return err
}
dst := filepath.Join(tmpdir, dst+"/"+hdr.Name)
if hdr.Typeflag == tar.TypeSymlink {
if err := symlink(hdr.Linkname, dst); err != nil {
return err
}
continue
}
if err := os.MkdirAll(filepath.Dir(dst), 0755); err != nil {
return err
}
f, err := os.OpenFile(dst, os.O_CREATE|os.O_EXCL|os.O_WRONLY, os.FileMode(hdr.Mode)&0777)
if err != nil {
return err
}
if _, err := io.Copy(f, tr); err != nil {
return err
}
if err := f.Close(); err != nil {
return err
}
}
return nil
}
func fetchNDK() error {
if useStrippedNDK {
if err := fetchStrippedNDK(); err != nil {
return err
}
} else {
if err := fetchFullNDK(); err != nil {
return err
}
}
if goos == "windows" {
resetReadOnlyFlagAll(filepath.Join(tmpdir, "android-"+ndkVersion))
}
// Copy the cross compiling clang and clang++ compilers
llvmsrc := filepath.Join(tmpdir, fmt.Sprintf(
"android-%s/toolchains/llvm/prebuilt", ndkVersion))
if goos == "windows" && ndkarch == "x86" {
llvmsrc = filepath.Join(llvmsrc, "windows")
} else {
llvmsrc = filepath.Join(llvmsrc, goos+"-"+ndkarch)
}
llvmdst := filepath.Join(ndk.Root(), "llvm")
if err := mkdir(llvmdst); err != nil {
return err
}
if err := move(llvmdst, llvmsrc, "lib64", "bin"); err != nil {
return err
}
for arch := range androidEnv {
toolchain := ndk.Toolchain(arch)
dst := filepath.Join(ndk.Root(), toolchain.arch)
dstSysroot := filepath.Join(dst, "sysroot")
if err := mkdir(dstSysroot); err != nil {
return err
}
srcSysroot := filepath.Join(tmpdir, fmt.Sprintf(
"android-%s/platforms/%s/arch-%s", ndkVersion, toolchain.platform, toolchain.arch))
if err := move(dstSysroot, srcSysroot, "usr"); err != nil {
return err
}
ndkpath := filepath.Join(tmpdir, fmt.Sprintf(
"android-%s/toolchains/%s/prebuilt", ndkVersion, toolchain.gcc))
if goos == "windows" && ndkarch == "x86" {
ndkpath = filepath.Join(ndkpath, "windows")
} else {
ndkpath = filepath.Join(ndkpath, goos+"-"+ndkarch)
}
if err := move(dst, ndkpath, "bin", "lib"); err != nil {
return err
}
linkpath := filepath.Join(dst, toolchain.toolPrefix+"/bin")
if err := mkdir(linkpath); err != nil {
return err
}
for _, name := range []string{"ld", "as", "nm"} {
if goos == "windows" {
name += ".exe"
}
if err := symlink(filepath.Join(dst, "bin", toolchain.toolPrefix+"-"+name), filepath.Join(linkpath, name)); err != nil {
return err
}
}
for _, name := range []string{"clang", "clang++"} {
if goos == "windows" {
name += ".exe"
}
if err := symlink(filepath.Join(llvmdst, "bin", name), filepath.Join(dst, "bin", toolchain.toolPrefix+"-"+name)); err != nil {
return err
}
}
if err := symlink(filepath.Join(llvmdst, "lib64"), filepath.Join(dst, "lib64")); err != nil {
return err
}
}
return nil
}
func fetchStrippedNDK() error {
url := "https://dl.google.com/go/mobile/gomobile-" + ndkVersion + "-" + goos + "-" + ndkarch + ".tar.gz"
archive, err := fetch(url)
if err != nil {
return err
}
return extract("", archive)
}
func fetchFullNDK() error {
url := "https://dl.google.com/android/repository/android-" + ndkVersion + "-" + goos + "-" + ndkarch + ".zip"
archive, err := fetch(url)
if err != nil {
return err
}
var inflate *exec.Cmd
if goos != "windows" {
inflate = exec.Command("unzip", archive)
} else {
inflate = exec.Command("unzip.exe", archive)
}
inflate.Dir = tmpdir
return runCmd(inflate)
}
// fetch reads a URL into $GOPATH/pkg/gomobile/dl and returns the path
// to the downloaded file. Downloading is skipped if the file is
// already present.
func fetch(url string) (dst string, err error) {
if err := mkdir(filepath.Join(gomobilepath, "dl")); err != nil {
return "", err
}
name := path.Base(url)
dst = filepath.Join(gomobilepath, "dl", name)
// Use what's in the cache if force update is not required.
if !initU {
if buildX {
printcmd("stat %s", dst)
}
if _, err = os.Stat(dst); err == nil {
return dst, nil
}
}
if buildX {
printcmd("curl -o%s %s", dst, url)
}
if buildN {
return dst, nil
}
if buildV {
fmt.Fprintf(os.Stderr, "Downloading %s.\n", url)
}
f, err := ioutil.TempFile(tmpdir, "partial-"+name)
if err != nil {
return "", err
}
defer func() {
if err != nil {
f.Close()
os.Remove(f.Name())
}
}()
hashw := sha256.New()
resp, err := http.Get(url)
if err != nil {
return "", err
}
if resp.StatusCode != http.StatusOK {
err = fmt.Errorf("error fetching %v, status: %v", url, resp.Status)
} else {
_, err = io.Copy(io.MultiWriter(hashw, f), resp.Body)
}
if err2 := resp.Body.Close(); err == nil {
err = err2
}
if err != nil {
return "", err
}
if err = f.Close(); err != nil {
return "", err
}
hash := hex.EncodeToString(hashw.Sum(nil))
if fetchHashes[name] != hash {
return "", fmt.Errorf("sha256 for %q: %v, want %v. Try 'gomobile clean'", name, hash, fetchHashes[name])
}
if err = os.Rename(f.Name(), dst); err != nil {
return "", err
}
return dst, nil
}
func doCopyAll(dst, src string) error {
return filepath.Walk(src, func(path string, info os.FileInfo, errin error) (err error) {
if errin != nil {
return errin
}
prefixLen := len(src)
if len(path) > prefixLen {
prefixLen++ // file separator
}
outpath := filepath.Join(dst, path[prefixLen:])
if info.IsDir() {
return os.Mkdir(outpath, 0755)
}
in, err := os.Open(path)
if err != nil {
return err
}
defer in.Close()
out, err := os.OpenFile(outpath, os.O_CREATE|os.O_EXCL|os.O_WRONLY, info.Mode())
if err != nil {
return err
}
defer func() {
if errc := out.Close(); err == nil {
err = errc
}
}()
_, err = io.Copy(out, in)
return err
})
}
func removeAll(path string) error {
if buildX || buildN {
printcmd(`rm -r -f "%s"`, path)
}
if buildN {
return nil
}
// os.RemoveAll behaves differently in windows.
// http://golang.org/issues/9606
if goos == "windows" {
resetReadOnlyFlagAll(path)
}
return os.RemoveAll(path)
}
func resetReadOnlyFlagAll(path string) error {
fi, err := os.Stat(path)
if err != nil {
return err
}
if !fi.IsDir() {
return os.Chmod(path, 0666)
}
fd, err := os.Open(path)
if err != nil {
return err
}
defer fd.Close()
names, _ := fd.Readdirnames(-1)
for _, name := range names {
resetReadOnlyFlagAll(path + string(filepath.Separator) + name)
}
return nil
}
func goEnv(name string) string {
if val := os.Getenv(name); val != "" {
return val
}
val, err := exec.Command("go", "env", name).Output()
if err != nil {
panic(err) // the Go tool was tested to work earlier
}
return strings.TrimSpace(string(val))
}
func runCmd(cmd *exec.Cmd) error {
if buildX || buildN {
dir := ""
if cmd.Dir != "" {
dir = "PWD=" + cmd.Dir + " "
}
env := strings.Join(cmd.Env, " ")
if env != "" {
env += " "
}
printcmd("%s%s%s", dir, env, strings.Join(cmd.Args, " "))
}
buf := new(bytes.Buffer)
buf.WriteByte('\n')
if buildV {
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
} else {
cmd.Stdout = buf
cmd.Stderr = buf
}
if buildWork {
if goos == "windows" {
cmd.Env = append(cmd.Env, `TEMP=`+tmpdir)
cmd.Env = append(cmd.Env, `TMP=`+tmpdir)
} else {
cmd.Env = append(cmd.Env, `TMPDIR=`+tmpdir)
}
}
if !buildN {
cmd.Env = environ(cmd.Env)
if err := cmd.Run(); err != nil {
return fmt.Errorf("%s failed: %v%s", strings.Join(cmd.Args, " "), err, buf)
}
}
return nil
}