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 }