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