github.com/acrespo/mobile@v0.0.0-20190107162257-dc0771356504/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  import (
     8  	"bytes"
     9  	"errors"
    10  	"fmt"
    11  	"io"
    12  	"io/ioutil"
    13  	"os"
    14  	"os/exec"
    15  	"path/filepath"
    16  	"runtime"
    17  	"strings"
    18  	"time"
    19  )
    20  
    21  var (
    22  	goos   = runtime.GOOS
    23  	goarch = runtime.GOARCH
    24  )
    25  
    26  var cmdInit = &command{
    27  	run:   runInit,
    28  	Name:  "init",
    29  	Usage: "[-ndk dir] [-openal dir]",
    30  	Short: "install NDK toolchains and build OpenAL for Android",
    31  	Long: `
    32  If the -ndk flag is specified or the Android NDK is installed at
    33  $ANDROID_HOME/ndk-bundle, init will create NDK standalone toolchains
    34  for Android targets.
    35  
    36  If a OpenAL source directory is specified with -openal, init will
    37  build an Android version of OpenAL for use with gomobile build
    38  and gomobile install.
    39  `,
    40  }
    41  
    42  var (
    43  	initNDK    string // -ndk
    44  	initOpenAL string // -openal
    45  )
    46  
    47  func init() {
    48  	cmdInit.flag.StringVar(&initNDK, "ndk", "", "Android NDK path")
    49  	cmdInit.flag.StringVar(&initOpenAL, "openal", "", "OpenAL source path")
    50  }
    51  
    52  func runInit(cmd *command) error {
    53  	gopaths := filepath.SplitList(goEnv("GOPATH"))
    54  	if len(gopaths) == 0 {
    55  		return fmt.Errorf("GOPATH is not set")
    56  	}
    57  	gomobilepath = filepath.Join(gopaths[0], "pkg/gomobile")
    58  
    59  	if buildX || buildN {
    60  		fmt.Fprintln(xout, "GOMOBILE="+gomobilepath)
    61  	}
    62  	removeAll(gomobilepath)
    63  
    64  	if err := mkdir(gomobilepath); err != nil {
    65  		return err
    66  	}
    67  
    68  	if buildN {
    69  		tmpdir = filepath.Join(gomobilepath, "work")
    70  	} else {
    71  		var err error
    72  		tmpdir, err = ioutil.TempDir(gomobilepath, "work-")
    73  		if err != nil {
    74  			return err
    75  		}
    76  	}
    77  	if buildX || buildN {
    78  		fmt.Fprintln(xout, "WORK="+tmpdir)
    79  	}
    80  	defer func() {
    81  		if buildWork {
    82  			fmt.Printf("WORK=%s\n", tmpdir)
    83  			return
    84  		}
    85  		removeAll(tmpdir)
    86  	}()
    87  
    88  	// Make sure gobind is up to date.
    89  	if err := goInstall([]string{"golang.org/x/mobile/cmd/gobind"}, nil); err != nil {
    90  		return err
    91  	}
    92  
    93  	if buildN {
    94  		initNDK = "$NDK_PATH"
    95  		initOpenAL = "$OPENAL_PATH"
    96  	} else {
    97  		toolsDir := filepath.Join("prebuilt", archNDK(), "bin")
    98  		// Try the ndk-bundle SDK package package, if installed.
    99  		if initNDK == "" {
   100  			if sdkHome := os.Getenv("ANDROID_HOME"); sdkHome != "" {
   101  				path := filepath.Join(sdkHome, "ndk-bundle")
   102  				if st, err := os.Stat(filepath.Join(path, toolsDir)); err == nil && st.IsDir() {
   103  					initNDK = path
   104  				}
   105  			}
   106  		}
   107  		if initNDK != "" {
   108  			var err error
   109  			if initNDK, err = filepath.Abs(initNDK); err != nil {
   110  				return err
   111  			}
   112  			// Check if the platform directory contains a known subdirectory.
   113  			if _, err := os.Stat(filepath.Join(initNDK, toolsDir)); err != nil {
   114  				if os.IsNotExist(err) {
   115  					return fmt.Errorf("%q does not point to an Android NDK.", initNDK)
   116  				}
   117  				return err
   118  			}
   119  		}
   120  		if initOpenAL != "" {
   121  			var err error
   122  			if initOpenAL, err = filepath.Abs(initOpenAL); err != nil {
   123  				return err
   124  			}
   125  		}
   126  	}
   127  	if err := envInit(); err != nil {
   128  		return err
   129  	}
   130  
   131  	start := time.Now()
   132  
   133  	if err := installNDKToolchains(gomobilepath); err != nil {
   134  		return err
   135  	}
   136  
   137  	if err := installOpenAL(gomobilepath); err != nil {
   138  		return err
   139  	}
   140  
   141  	if buildV {
   142  		took := time.Since(start) / time.Second * time.Second
   143  		fmt.Fprintf(os.Stderr, "\nDone, build took %s.\n", took)
   144  	}
   145  	return nil
   146  }
   147  
   148  func installNDKToolchains(gomobilepath string) error {
   149  	if initNDK == "" {
   150  		return nil
   151  	}
   152  	toolsDir := filepath.Join(initNDK, "prebuilt", archNDK(), "bin")
   153  	py27 := filepath.Join(toolsDir, "python2.7")
   154  	for _, arch := range allArchs {
   155  		t := ndk[arch]
   156  		// Split android-XX to get the api version.
   157  		platform := strings.SplitN(t.platform, "-", 2)
   158  		api := platform[1]
   159  		cmd := exec.Command(py27,
   160  			"build/tools/make_standalone_toolchain.py",
   161  			"--arch="+t.arch,
   162  			"--api="+api,
   163  			"--install-dir="+filepath.Join(gomobilepath, "ndk-toolchains", t.arch))
   164  		cmd.Dir = initNDK
   165  		if err := runCmd(cmd); err != nil {
   166  			return err
   167  		}
   168  	}
   169  	return nil
   170  }
   171  
   172  func installOpenAL(gomobilepath string) error {
   173  	if initOpenAL == "" {
   174  		return nil
   175  	}
   176  	if !hasNDK() {
   177  		return errors.New("The Android NDK is needed to build OpenAL but it was not found. Please run gomobile init with the ndk-bundle installed through the Android SDK manager or with the -ndk flag set.")
   178  	}
   179  
   180  	var cmake string
   181  	if buildN {
   182  		cmake = "cmake"
   183  	} else {
   184  		sdkRoot := os.Getenv("ANDROID_HOME")
   185  		if sdkRoot == "" {
   186  			return nil
   187  		}
   188  		var err error
   189  		cmake, err = exec.LookPath("cmake")
   190  		if err != nil {
   191  			cmakePath := filepath.Join(sdkRoot, "cmake")
   192  			cmakeDir, err := os.Open(cmakePath)
   193  			if err != nil {
   194  				if os.IsNotExist(err) {
   195  					// Skip OpenAL install if the cmake package is not installed.
   196  					return errors.New("cmake was not found in the PATH. Please install it through the Android SDK manager.")
   197  				}
   198  				return err
   199  			}
   200  			defer cmakeDir.Close()
   201  			// There might be multiple versions of CMake installed. Use any one for now.
   202  			cmakeVers, err := cmakeDir.Readdirnames(1)
   203  			if err != nil || len(cmakeVers) == 0 {
   204  				return errors.New("cmake was not found in the PATH. Please install it through the Android SDK manager.")
   205  			}
   206  			cmake = filepath.Join(cmakePath, cmakeVers[0], "bin", "cmake")
   207  		}
   208  	}
   209  	var alTmpDir string
   210  	if buildN {
   211  		alTmpDir = filepath.Join(gomobilepath, "work")
   212  	} else {
   213  		var err error
   214  		alTmpDir, err = ioutil.TempDir(gomobilepath, "openal-release-")
   215  		if err != nil {
   216  			return err
   217  		}
   218  		defer removeAll(alTmpDir)
   219  	}
   220  
   221  	for _, f := range []string{"include/AL/al.h", "include/AL/alc.h"} {
   222  		dst := filepath.Join(gomobilepath, f)
   223  		src := filepath.Join(initOpenAL, f)
   224  		if err := copyFile(dst, src); err != nil {
   225  			return err
   226  		}
   227  	}
   228  
   229  	for _, arch := range allArchs {
   230  		t := ndk[arch]
   231  		abi := t.arch
   232  		if abi == "arm" {
   233  			abi = "armeabi"
   234  		}
   235  		tcPath := filepath.Join(gomobilepath, "ndk-toolchains", t.arch, "bin")
   236  		make := filepath.Join(tcPath, "make")
   237  		// Split android-XX to get the api version.
   238  		buildDir := alTmpDir + "/build/" + abi
   239  		if err := mkdir(buildDir); err != nil {
   240  			return err
   241  		}
   242  		cmd := exec.Command(cmake,
   243  			initOpenAL,
   244  			"-DCMAKE_TOOLCHAIN_FILE="+initOpenAL+"/XCompile-Android.txt",
   245  			"-DHOST="+t.toolPrefix)
   246  		cmd.Dir = buildDir
   247  		if !buildN {
   248  			orgPath := os.Getenv("PATH")
   249  			cmd.Env = []string{"PATH=" + tcPath + string(os.PathListSeparator) + orgPath}
   250  		}
   251  		if err := runCmd(cmd); err != nil {
   252  			return err
   253  		}
   254  
   255  		cmd = exec.Command(make)
   256  		cmd.Dir = buildDir
   257  		if err := runCmd(cmd); err != nil {
   258  			return err
   259  		}
   260  
   261  		dst := filepath.Join(gomobilepath, "lib", t.abi, "libopenal.so")
   262  		src := filepath.Join(alTmpDir, "build", abi, "libopenal.so")
   263  		if err := copyFile(dst, src); err != nil {
   264  			return err
   265  		}
   266  	}
   267  	return nil
   268  }
   269  
   270  var commonPkgs = []string{
   271  	"golang.org/x/mobile/gl",
   272  	"golang.org/x/mobile/app",
   273  	"golang.org/x/mobile/exp/app/debug",
   274  }
   275  
   276  func mkdir(dir string) error {
   277  	if buildX || buildN {
   278  		printcmd("mkdir -p %s", dir)
   279  	}
   280  	if buildN {
   281  		return nil
   282  	}
   283  	return os.MkdirAll(dir, 0755)
   284  }
   285  
   286  func symlink(src, dst string) error {
   287  	if buildX || buildN {
   288  		printcmd("ln -s %s %s", src, dst)
   289  	}
   290  	if buildN {
   291  		return nil
   292  	}
   293  	if goos == "windows" {
   294  		return doCopyAll(dst, src)
   295  	}
   296  	return os.Symlink(src, dst)
   297  }
   298  
   299  func rm(name string) error {
   300  	if buildX || buildN {
   301  		printcmd("rm %s", name)
   302  	}
   303  	if buildN {
   304  		return nil
   305  	}
   306  	return os.Remove(name)
   307  }
   308  
   309  func doCopyAll(dst, src string) error {
   310  	return filepath.Walk(src, func(path string, info os.FileInfo, errin error) (err error) {
   311  		if errin != nil {
   312  			return errin
   313  		}
   314  		prefixLen := len(src)
   315  		if len(path) > prefixLen {
   316  			prefixLen++ // file separator
   317  		}
   318  		outpath := filepath.Join(dst, path[prefixLen:])
   319  		if info.IsDir() {
   320  			return os.Mkdir(outpath, 0755)
   321  		}
   322  		in, err := os.Open(path)
   323  		if err != nil {
   324  			return err
   325  		}
   326  		defer in.Close()
   327  		out, err := os.OpenFile(outpath, os.O_CREATE|os.O_EXCL|os.O_WRONLY, info.Mode())
   328  		if err != nil {
   329  			return err
   330  		}
   331  		defer func() {
   332  			if errc := out.Close(); err == nil {
   333  				err = errc
   334  			}
   335  		}()
   336  		_, err = io.Copy(out, in)
   337  		return err
   338  	})
   339  }
   340  
   341  func removeAll(path string) error {
   342  	if buildX || buildN {
   343  		printcmd(`rm -r -f "%s"`, path)
   344  	}
   345  	if buildN {
   346  		return nil
   347  	}
   348  
   349  	// os.RemoveAll behaves differently in windows.
   350  	// http://golang.org/issues/9606
   351  	if goos == "windows" {
   352  		resetReadOnlyFlagAll(path)
   353  	}
   354  
   355  	return os.RemoveAll(path)
   356  }
   357  
   358  func resetReadOnlyFlagAll(path string) error {
   359  	fi, err := os.Stat(path)
   360  	if err != nil {
   361  		return err
   362  	}
   363  	if !fi.IsDir() {
   364  		return os.Chmod(path, 0666)
   365  	}
   366  	fd, err := os.Open(path)
   367  	if err != nil {
   368  		return err
   369  	}
   370  	defer fd.Close()
   371  
   372  	names, _ := fd.Readdirnames(-1)
   373  	for _, name := range names {
   374  		resetReadOnlyFlagAll(path + string(filepath.Separator) + name)
   375  	}
   376  	return nil
   377  }
   378  
   379  func goEnv(name string) string {
   380  	if val := os.Getenv(name); val != "" {
   381  		return val
   382  	}
   383  	val, err := exec.Command("go", "env", name).Output()
   384  	if err != nil {
   385  		panic(err) // the Go tool was tested to work earlier
   386  	}
   387  	return strings.TrimSpace(string(val))
   388  }
   389  
   390  func runCmd(cmd *exec.Cmd) error {
   391  	if buildX || buildN {
   392  		dir := ""
   393  		if cmd.Dir != "" {
   394  			dir = "PWD=" + cmd.Dir + " "
   395  		}
   396  		env := strings.Join(cmd.Env, " ")
   397  		if env != "" {
   398  			env += " "
   399  		}
   400  		printcmd("%s%s%s", dir, env, strings.Join(cmd.Args, " "))
   401  	}
   402  
   403  	buf := new(bytes.Buffer)
   404  	buf.WriteByte('\n')
   405  	if buildV {
   406  		cmd.Stdout = os.Stdout
   407  		cmd.Stderr = os.Stderr
   408  	} else {
   409  		cmd.Stdout = buf
   410  		cmd.Stderr = buf
   411  	}
   412  
   413  	if buildWork {
   414  		if goos == "windows" {
   415  			cmd.Env = append(cmd.Env, `TEMP=`+tmpdir)
   416  			cmd.Env = append(cmd.Env, `TMP=`+tmpdir)
   417  		} else {
   418  			cmd.Env = append(cmd.Env, `TMPDIR=`+tmpdir)
   419  		}
   420  	}
   421  
   422  	if !buildN {
   423  		cmd.Env = environ(cmd.Env)
   424  		if err := cmd.Run(); err != nil {
   425  			return fmt.Errorf("%s failed: %v%s", strings.Join(cmd.Args, " "), err, buf)
   426  		}
   427  	}
   428  	return nil
   429  }