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 }