gomobile.go 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. // Copyright 2019 The Ebiten Authors
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package main
  15. import (
  16. _ "embed"
  17. "fmt"
  18. "os"
  19. "os/exec"
  20. "path/filepath"
  21. "regexp"
  22. "runtime"
  23. "runtime/debug"
  24. // Add a dependency on gomobile in order to get the version via debug.ReadBuildInfo().
  25. _ "github.com/ebitengine/gomobile/geom"
  26. )
  27. //go:embed gobind.go
  28. var gobind_go []byte
  29. //go:embed _files/EbitenViewController.m
  30. var objcM []byte
  31. //go:embed _files/EbitenView.java
  32. var viewJava []byte
  33. //go:embed _files/EbitenSurfaceView.java
  34. var surfaceViewJava []byte
  35. func runCommand(command string, args []string, env []string) error {
  36. if buildX || buildN {
  37. for _, e := range env {
  38. fmt.Printf("%s ", e)
  39. }
  40. fmt.Print(command)
  41. for _, arg := range args {
  42. fmt.Printf(" %s", arg)
  43. }
  44. fmt.Println()
  45. }
  46. if buildN {
  47. return nil
  48. }
  49. cmd := exec.Command(command, args...)
  50. if len(env) > 0 {
  51. cmd.Env = append(os.Environ(), env...)
  52. }
  53. if out, err := cmd.CombinedOutput(); err != nil {
  54. return fmt.Errorf("%s %v failed: %v\n%v", command, args, string(out), err)
  55. }
  56. return nil
  57. }
  58. func removeAll(path string) error {
  59. if buildX || buildN {
  60. fmt.Printf("rm -rf %s\n", path)
  61. }
  62. if buildN {
  63. return nil
  64. }
  65. return os.RemoveAll(path)
  66. }
  67. func runGo(args ...string) error {
  68. return runCommand("go", args, nil)
  69. }
  70. // exe adds the .exe extension to the given filename.
  71. // Without .exe, the executable won't be found by exec.LookPath on Windows (#1096).
  72. func exe(filename string) string {
  73. if runtime.GOOS == "windows" {
  74. return filename + ".exe"
  75. }
  76. return filename
  77. }
  78. func prepareGomobileCommands() (string, error) {
  79. tmp, err := os.MkdirTemp("", "ebitenmobile-")
  80. if err != nil {
  81. return "", err
  82. }
  83. newpath := filepath.Join(tmp, "bin")
  84. if path := os.Getenv("PATH"); path != "" {
  85. newpath += string(filepath.ListSeparator) + path
  86. }
  87. if buildX || buildN {
  88. fmt.Printf("PATH=%s\n", newpath)
  89. }
  90. if !buildN {
  91. if err := os.Setenv("PATH", newpath); err != nil {
  92. return tmp, err
  93. }
  94. }
  95. pwd, err := os.Getwd()
  96. if err != nil {
  97. return tmp, err
  98. }
  99. // cd
  100. if buildX {
  101. fmt.Printf("cd %s\n", tmp)
  102. }
  103. if err := os.Chdir(tmp); err != nil {
  104. return tmp, err
  105. }
  106. defer func() {
  107. _ = os.Chdir(pwd)
  108. }()
  109. const (
  110. modname = "ebitenmobiletemporary"
  111. buildtags = "//go:build tools"
  112. )
  113. if err := runGo("mod", "init", modname); err != nil {
  114. return tmp, err
  115. }
  116. if err := os.WriteFile("tools.go", []byte(fmt.Sprintf(`%s
  117. package %s
  118. import (
  119. _ "github.com/ebitengine/gomobile/cmd/gobind"
  120. _ "github.com/ebitengine/gomobile/cmd/gomobile"
  121. )
  122. `, buildtags, modname)), 0644); err != nil {
  123. return tmp, err
  124. }
  125. h, err := gomobileHash()
  126. if err != nil {
  127. return tmp, err
  128. }
  129. // To record gomobile to go.sum for Go 1.16 and later, go-get gomobile instead of github.com/ebitengine/gomobile (#1487).
  130. // This also records gobind as gomobile depends on gobind indirectly.
  131. // Using `...` doesn't work on Windows since mobile/internal/mobileinit cannot be compiled on Windows w/o Cgo (#1493).
  132. if err := runGo("get", "github.com/ebitengine/gomobile/cmd/gomobile@"+h); err != nil {
  133. return tmp, err
  134. }
  135. if localgm := os.Getenv("EBITENMOBILE_GOMOBILE"); localgm != "" {
  136. if !filepath.IsAbs(localgm) {
  137. localgm = filepath.Join(pwd, localgm)
  138. }
  139. if err := runGo("mod", "edit", "-replace=github.com/ebitengine/gomobile="+localgm); err != nil {
  140. return tmp, err
  141. }
  142. }
  143. // runtime.Version() is the current executing Go version. For example, this is the version of the toolchain directive in go.mod.
  144. // This might differ from the Go command version under the temporary directory.
  145. // To avoid the version mismatch, set the toolchain explicitly (#3086).
  146. t, err := toolchainParameter()
  147. if err != nil {
  148. return tmp, err
  149. }
  150. if err := runGo("mod", "edit", "-toolchain="+t); err != nil {
  151. return tmp, err
  152. }
  153. if err := runGo("mod", "tidy"); err != nil {
  154. return tmp, err
  155. }
  156. if err := runGo("build", "-o", exe(filepath.Join("bin", "gomobile")), "github.com/ebitengine/gomobile/cmd/gomobile"); err != nil {
  157. return tmp, err
  158. }
  159. if err := runGo("build", "-o", exe(filepath.Join("bin", "gobind-original")), "github.com/ebitengine/gomobile/cmd/gobind"); err != nil {
  160. return tmp, err
  161. }
  162. if err := os.Mkdir("src", 0755); err != nil {
  163. return tmp, err
  164. }
  165. if err := os.WriteFile(filepath.Join("src", "gobind.go"), gobind_go, 0644); err != nil {
  166. return tmp, err
  167. }
  168. if err := os.Mkdir(filepath.Join("src", "_files"), 0755); err != nil {
  169. return tmp, err
  170. }
  171. if err := os.WriteFile(filepath.Join("src", "_files", "EbitenViewController.m"), objcM, 0644); err != nil {
  172. return tmp, err
  173. }
  174. if err := os.WriteFile(filepath.Join("src", "_files", "EbitenView.java"), viewJava, 0644); err != nil {
  175. return tmp, err
  176. }
  177. if err := os.WriteFile(filepath.Join("src", "_files", "EbitenSurfaceView.java"), surfaceViewJava, 0644); err != nil {
  178. return tmp, err
  179. }
  180. // The newly added Go files like gobind.go might add new dependencies.
  181. if err := runGo("mod", "tidy"); err != nil {
  182. return tmp, err
  183. }
  184. if err := runGo("build", "-o", exe(filepath.Join("bin", "gobind")), "-tags", "ebitenmobilegobind", filepath.Join("src", "gobind.go")); err != nil {
  185. return tmp, err
  186. }
  187. if err := runCommand("gomobile", []string{"init"}, nil); err != nil {
  188. return tmp, err
  189. }
  190. return tmp, nil
  191. }
  192. func toolchainParameter() (string, error) {
  193. pattern := regexp.MustCompile(`\bgo\d+\.\d+(\.\d+)?`)
  194. rawVersion := runtime.Version()
  195. m := pattern.FindStringSubmatch(rawVersion)
  196. if len(m) == 0 {
  197. return "", fmt.Errorf("ebitenmobile: unexpected version: %s", rawVersion)
  198. }
  199. return m[0], nil
  200. }
  201. func gomobileHash() (string, error) {
  202. info, ok := debug.ReadBuildInfo()
  203. if !ok {
  204. return "", fmt.Errorf("ebitenmobile: debug.ReadBuildInfo failed")
  205. }
  206. for _, m := range info.Deps {
  207. if m.Path == "github.com/ebitengine/gomobile" {
  208. return m.Version, nil
  209. }
  210. }
  211. return "", fmt.Errorf("ebitenmobile: getting the gomobile version failed")
  212. }