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