Unfortunately GLES is not a common package for a linux desktop to have installed. To avoid bind-based users having to jump through the hoop of installing GL, disable this performance optimization on linux for now. Change-Id: Ic00baef6237dca360f191ec291b6c165eda7ad6e Reviewed-on: https://go-review.googlesource.com/13874 Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
678 lines
15 KiB
Go
678 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
|
|
|
|
// TODO(crawshaw): android/{386,arm64}
|
|
|
|
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 GCC
|
|
// 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-r10e"
|
|
const openALVersion = "openal-soft-1.16.0.1"
|
|
|
|
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 {
|
|
version, err := goVersion()
|
|
if err != nil {
|
|
return fmt.Errorf("%v: %s", err, version)
|
|
}
|
|
|
|
gopaths := filepath.SplitList(goEnv("GOPATH"))
|
|
if len(gopaths) == 0 {
|
|
return fmt.Errorf("GOPATH is not set")
|
|
}
|
|
gomobilepath = filepath.Join(gopaths[0], "pkg/gomobile")
|
|
ndkccpath = filepath.Join(gopaths[0], "pkg/gomobile/android-"+ndkVersion)
|
|
verpath := filepath.Join(gopaths[0], "pkg/gomobile/version")
|
|
if buildX || buildN {
|
|
fmt.Fprintln(xout, "GOMOBILE="+gomobilepath)
|
|
}
|
|
removeGomobilepkg()
|
|
|
|
if err := mkdir(ndkccpath); 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 := fetchNDK(); err != nil {
|
|
return err
|
|
}
|
|
if err := fetchOpenAL(); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := envInit(); 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()
|
|
if err := installStd(androidArmEnv); 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, version, 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 goVersion() ([]byte, error) {
|
|
gobin, err := exec.LookPath("go")
|
|
if err != nil {
|
|
return nil, fmt.Errorf(`no Go tool on $PATH`)
|
|
}
|
|
buildHelp, err := exec.Command(gobin, "help", "build").CombinedOutput()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("bad Go tool: %v (%s)", err, buildHelp)
|
|
}
|
|
// TODO(crawshaw): this is a crude test for Go 1.5. After release,
|
|
// remove this and check it is not an old release version.
|
|
if !bytes.Contains(buildHelp, []byte("-pkgdir")) {
|
|
return nil, fmt.Errorf("installed Go tool does not support -pkgdir")
|
|
}
|
|
return exec.Command(gobin, "version").CombinedOutput()
|
|
}
|
|
|
|
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"))
|
|
}
|
|
dst := filepath.Join(ndkccpath, "arm", "sysroot", "usr", "include")
|
|
src := filepath.Join(tmpdir, "openal", "include")
|
|
if err := move(dst, src, "AL"); err != nil {
|
|
return err
|
|
}
|
|
libDst := filepath.Join(ndkccpath, "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))
|
|
}
|
|
|
|
dst := filepath.Join(ndkccpath, "arm")
|
|
dstSysroot := filepath.Join(dst, "sysroot/usr")
|
|
if err := mkdir(dstSysroot); err != nil {
|
|
return err
|
|
}
|
|
|
|
srcSysroot := filepath.Join(tmpdir, "android-"+ndkVersion+"/platforms/android-15/arch-arm/usr")
|
|
if err := move(dstSysroot, srcSysroot, "include", "lib"); err != nil {
|
|
return err
|
|
}
|
|
|
|
ndkpath := filepath.Join(tmpdir, "android-"+ndkVersion+"/toolchains/arm-linux-androideabi-4.8/prebuilt")
|
|
if goos == "windows" && ndkarch == "x86" {
|
|
ndkpath = filepath.Join(ndkpath, "windows")
|
|
} else {
|
|
ndkpath = filepath.Join(ndkpath, goos+"-"+ndkarch)
|
|
}
|
|
if err := move(dst, ndkpath, "bin", "lib", "libexec"); err != nil {
|
|
return err
|
|
}
|
|
|
|
linkpath := filepath.Join(dst, "arm-linux-androideabi/bin")
|
|
if err := mkdir(linkpath); err != nil {
|
|
return err
|
|
}
|
|
for _, name := range []string{"ld", "as", "gcc", "g++"} {
|
|
if goos == "windows" {
|
|
name += ".exe"
|
|
}
|
|
if err := symlink(filepath.Join(dst, "bin", "arm-linux-androideabi-"+name), filepath.Join(linkpath, name)); 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/ndk/android-" + ndkVersion + "-" + goos + "-" + ndkarch + "."
|
|
if goos == "windows" {
|
|
url += "exe"
|
|
} else {
|
|
url += "bin"
|
|
}
|
|
archive, err := fetch(url)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// The self-extracting ndk dist file for Windows terminates
|
|
// with an error (error code 2 - corrupted or incomplete file)
|
|
// but there are no details on what caused this.
|
|
//
|
|
// Strangely, if the file is launched from file browser or
|
|
// unzipped with 7z.exe no error is reported.
|
|
//
|
|
// In general we use the stripped NDK, so this code path
|
|
// is not used, and 7z.exe is not a normal dependency.
|
|
var inflate *exec.Cmd
|
|
if goos != "windows" {
|
|
// The downloaded archive is executed on linux and os x to unarchive.
|
|
// To do this execute permissions are needed.
|
|
os.Chmod(archive, 0755)
|
|
|
|
inflate = exec.Command(archive)
|
|
} else {
|
|
inflate = exec.Command("7z.exe", "x", 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", 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
|
|
}
|