init.go 7.4 KB

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