blob: 79d6d88a10b74bd96220c845600bfd22aad5d767 [file] [log] [blame]
// 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 (
"bytes"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"time"
)
var (
goos = runtime.GOOS
goarch = runtime.GOARCH
)
var cmdInit = &command{
run: runInit,
Name: "init",
Usage: "[-ndk dir] [-openal dir]",
Short: "install NDK toolchains and build OpenAL for Android",
Long: `
If the -ndk flag is specified or the Android NDK is installed at
$ANDROID_HOME/ndk-bundle, init will create NDK standalone toolchains
for Android targets.
If a OpenAL source directory is specified with -openal, init will
build an Android version of OpenAL for use with gomobile build
and gomobile install.
`,
}
var (
initNDK string // -ndk
initOpenAL string // -openal
)
func init() {
cmdInit.flag.StringVar(&initNDK, "ndk", "", "Android NDK path")
cmdInit.flag.StringVar(&initOpenAL, "openal", "", "OpenAL source path")
}
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")
if buildX || buildN {
fmt.Fprintln(xout, "GOMOBILE="+gomobilepath)
}
removeAll(gomobilepath)
if err := mkdir(gomobilepath); 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)
}()
// Make sure gobind is up to date.
if err := goInstall([]string{"golang.org/x/mobile/cmd/gobind"}, nil); err != nil {
return err
}
if buildN {
initNDK = "$NDK_PATH"
initOpenAL = "$OPENAL_PATH"
} else {
toolsDir := filepath.Join("prebuilt", archNDK(), "bin")
// Try the ndk-bundle SDK package package, if installed.
if initNDK == "" {
if sdkHome := os.Getenv("ANDROID_HOME"); sdkHome != "" {
path := filepath.Join(sdkHome, "ndk-bundle")
if st, err := os.Stat(filepath.Join(path, toolsDir)); err == nil && st.IsDir() {
initNDK = path
}
}
}
if initNDK != "" {
var err error
if initNDK, err = filepath.Abs(initNDK); err != nil {
return err
}
// Check if the platform directory contains a known subdirectory.
if _, err := os.Stat(filepath.Join(initNDK, toolsDir)); err != nil {
if os.IsNotExist(err) {
return fmt.Errorf("%q does not point to an Android NDK.", initNDK)
}
return err
}
}
if initOpenAL != "" {
var err error
if initOpenAL, err = filepath.Abs(initOpenAL); err != nil {
return err
}
}
}
if err := envInit(); err != nil {
return err
}
start := time.Now()
if err := installNDKToolchains(gomobilepath); err != nil {
return err
}
if err := installOpenAL(gomobilepath); 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
}
func installNDKToolchains(gomobilepath string) error {
if initNDK == "" {
return nil
}
toolsDir := filepath.Join(initNDK, "prebuilt", archNDK(), "bin")
py27 := filepath.Join(toolsDir, "python2.7")
for _, arch := range archs {
t := ndk[arch]
// Split android-XX to get the api version.
platform := strings.SplitN(t.platform, "-", 2)
api := platform[1]
cmd := exec.Command(py27,
"build/tools/make_standalone_toolchain.py",
"--arch="+t.arch,
"--api="+api,
"--install-dir="+filepath.Join(gomobilepath, "ndk-toolchains", t.arch))
cmd.Dir = initNDK
if err := runCmd(cmd); err != nil {
return err
}
}
return nil
}
func installOpenAL(gomobilepath string) error {
if initNDK == "" || initOpenAL == "" {
return nil
}
var cmake string
if buildN {
cmake = "cmake"
} else {
sdkRoot := os.Getenv("ANDROID_HOME")
if sdkRoot == "" {
return nil
}
var err error
cmake, err = exec.LookPath("cmake")
if err != nil {
cmakePath := filepath.Join(sdkRoot, "cmake")
cmakeDir, err := os.Open(cmakePath)
if err != nil {
if os.IsNotExist(err) {
// Skip OpenAL install if the cmake package is not installed.
return errors.New("cmake was not found in the PATH. Please install it through the Android SDK manager.")
}
return err
}
defer cmakeDir.Close()
// There might be multiple versions of CMake installed. Use any one for now.
cmakeVers, err := cmakeDir.Readdirnames(1)
if err != nil || len(cmakeVers) == 0 {
return errors.New("cmake was not found in the PATH. Please install it through the Android SDK manager.")
}
cmake = filepath.Join(cmakePath, cmakeVers[0], "bin", "cmake")
}
}
var alTmpDir string
if buildN {
alTmpDir = filepath.Join(gomobilepath, "work")
} else {
var err error
alTmpDir, err = ioutil.TempDir(gomobilepath, "openal-release-")
if err != nil {
return err
}
defer removeAll(alTmpDir)
}
for _, f := range []string{"include/AL/al.h", "include/AL/alc.h"} {
dst := filepath.Join(gomobilepath, f)
src := filepath.Join(initOpenAL, f)
if err := copyFile(dst, src); err != nil {
return err
}
}
for _, arch := range archs {
t := ndk[arch]
abi := t.arch
if abi == "arm" {
abi = "armeabi"
}
tcPath := filepath.Join(gomobilepath, "ndk-toolchains", t.arch, "bin")
make := filepath.Join(tcPath, "make")
// Split android-XX to get the api version.
buildDir := alTmpDir + "/build/" + abi
if err := mkdir(buildDir); err != nil {
return err
}
cmd := exec.Command(cmake,
initOpenAL,
"-DCMAKE_TOOLCHAIN_FILE="+initOpenAL+"/XCompile-Android.txt",
"-DHOST="+t.toolPrefix)
cmd.Dir = buildDir
if !buildN {
orgPath := os.Getenv("PATH")
cmd.Env = []string{"PATH=" + tcPath + string(os.PathListSeparator) + orgPath}
}
if err := runCmd(cmd); err != nil {
return err
}
cmd = exec.Command(make)
cmd.Dir = buildDir
if err := runCmd(cmd); err != nil {
return err
}
dst := filepath.Join(gomobilepath, "lib", t.abi, "libopenal.so")
src := filepath.Join(alTmpDir, "build", abi, "libopenal.so")
if err := copyFile(dst, src); err != nil {
return err
}
}
return nil
}
var commonPkgs = []string{
"golang.org/x/mobile/gl",
"golang.org/x/mobile/app",
"golang.org/x/mobile/exp/app/debug",
}
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 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
}