install_deps.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. // install_deps.go - iOS development dependency checker
  2. // This script checks for required iOS development tools.
  3. // It's designed to be portable across different shells by using Go instead of shell scripts.
  4. //
  5. // Usage:
  6. // go run install_deps.go # Interactive mode
  7. // TASK_FORCE_YES=true go run install_deps.go # Auto-accept prompts
  8. // CI=true go run install_deps.go # CI mode (auto-accept)
  9. package main
  10. import (
  11. "bufio"
  12. "fmt"
  13. "os"
  14. "os/exec"
  15. "strings"
  16. )
  17. type Dependency struct {
  18. Name string
  19. CheckFunc func() (bool, string) // Returns (success, details)
  20. Required bool
  21. InstallCmd []string
  22. InstallMsg string
  23. SuccessMsg string
  24. FailureMsg string
  25. }
  26. func main() {
  27. fmt.Println("Checking iOS development dependencies...")
  28. fmt.Println("=" + strings.Repeat("=", 50))
  29. fmt.Println()
  30. hasErrors := false
  31. dependencies := []Dependency{
  32. {
  33. Name: "Xcode",
  34. CheckFunc: func() (bool, string) {
  35. // Check if xcodebuild exists
  36. if !checkCommand([]string{"xcodebuild", "-version"}) {
  37. return false, ""
  38. }
  39. // Get version info
  40. out, err := exec.Command("xcodebuild", "-version").Output()
  41. if err != nil {
  42. return false, ""
  43. }
  44. lines := strings.Split(string(out), "\n")
  45. if len(lines) > 0 {
  46. return true, strings.TrimSpace(lines[0])
  47. }
  48. return true, ""
  49. },
  50. Required: true,
  51. InstallMsg: "Please install Xcode from the Mac App Store:\n https://apps.apple.com/app/xcode/id497799835\n Xcode is REQUIRED for iOS development (includes iOS SDKs, simulators, and frameworks)",
  52. SuccessMsg: "✅ Xcode found",
  53. FailureMsg: "❌ Xcode not found (REQUIRED)",
  54. },
  55. {
  56. Name: "Xcode Developer Path",
  57. CheckFunc: func() (bool, string) {
  58. // Check if xcode-select points to a valid Xcode path
  59. out, err := exec.Command("xcode-select", "-p").Output()
  60. if err != nil {
  61. return false, "xcode-select not configured"
  62. }
  63. path := strings.TrimSpace(string(out))
  64. // Check if path exists and is in Xcode.app
  65. if _, err := os.Stat(path); err != nil {
  66. return false, "Invalid Xcode path"
  67. }
  68. // Verify it's pointing to Xcode.app (not just Command Line Tools)
  69. if !strings.Contains(path, "Xcode.app") {
  70. return false, fmt.Sprintf("Points to %s (should be Xcode.app)", path)
  71. }
  72. return true, path
  73. },
  74. Required: true,
  75. InstallCmd: []string{"sudo", "xcode-select", "-s", "/Applications/Xcode.app/Contents/Developer"},
  76. InstallMsg: "Xcode developer path needs to be configured",
  77. SuccessMsg: "✅ Xcode developer path configured",
  78. FailureMsg: "❌ Xcode developer path not configured correctly",
  79. },
  80. {
  81. Name: "iOS SDK",
  82. CheckFunc: func() (bool, string) {
  83. // Get the iOS Simulator SDK path
  84. cmd := exec.Command("xcrun", "--sdk", "iphonesimulator", "--show-sdk-path")
  85. output, err := cmd.Output()
  86. if err != nil {
  87. return false, "Cannot find iOS SDK"
  88. }
  89. sdkPath := strings.TrimSpace(string(output))
  90. // Check if the SDK path exists
  91. if _, err := os.Stat(sdkPath); err != nil {
  92. return false, "iOS SDK path not found"
  93. }
  94. // Check for UIKit framework (essential for iOS development)
  95. uikitPath := fmt.Sprintf("%s/System/Library/Frameworks/UIKit.framework", sdkPath)
  96. if _, err := os.Stat(uikitPath); err != nil {
  97. return false, "UIKit.framework not found"
  98. }
  99. // Get SDK version
  100. versionCmd := exec.Command("xcrun", "--sdk", "iphonesimulator", "--show-sdk-version")
  101. versionOut, _ := versionCmd.Output()
  102. version := strings.TrimSpace(string(versionOut))
  103. return true, fmt.Sprintf("iOS %s SDK", version)
  104. },
  105. Required: true,
  106. InstallMsg: "iOS SDK comes with Xcode. Please ensure Xcode is properly installed.",
  107. SuccessMsg: "✅ iOS SDK found with UIKit framework",
  108. FailureMsg: "❌ iOS SDK not found or incomplete",
  109. },
  110. {
  111. Name: "iOS Simulator Runtime",
  112. CheckFunc: func() (bool, string) {
  113. if !checkCommand([]string{"xcrun", "simctl", "help"}) {
  114. return false, ""
  115. }
  116. // Check if we can list runtimes
  117. out, err := exec.Command("xcrun", "simctl", "list", "runtimes").Output()
  118. if err != nil {
  119. return false, "Cannot access simulator"
  120. }
  121. // Count iOS runtimes
  122. lines := strings.Split(string(out), "\n")
  123. count := 0
  124. var versions []string
  125. for _, line := range lines {
  126. if strings.Contains(line, "iOS") && !strings.Contains(line, "unavailable") {
  127. count++
  128. // Extract version number
  129. if parts := strings.Fields(line); len(parts) > 2 {
  130. for _, part := range parts {
  131. if strings.HasPrefix(part, "(") && strings.HasSuffix(part, ")") {
  132. versions = append(versions, strings.Trim(part, "()"))
  133. break
  134. }
  135. }
  136. }
  137. }
  138. }
  139. if count > 0 {
  140. return true, fmt.Sprintf("%d runtime(s): %s", count, strings.Join(versions, ", "))
  141. }
  142. return false, "No iOS runtimes installed"
  143. },
  144. Required: true,
  145. InstallMsg: "iOS Simulator runtimes come with Xcode. You may need to download them:\n Xcode → Settings → Platforms → iOS",
  146. SuccessMsg: "✅ iOS Simulator runtime available",
  147. FailureMsg: "❌ iOS Simulator runtime not available",
  148. },
  149. }
  150. // Check each dependency
  151. for _, dep := range dependencies {
  152. success, details := dep.CheckFunc()
  153. if success {
  154. msg := dep.SuccessMsg
  155. if details != "" {
  156. msg = fmt.Sprintf("%s (%s)", dep.SuccessMsg, details)
  157. }
  158. fmt.Println(msg)
  159. } else {
  160. fmt.Println(dep.FailureMsg)
  161. if details != "" {
  162. fmt.Printf(" Details: %s\n", details)
  163. }
  164. if dep.Required {
  165. hasErrors = true
  166. if len(dep.InstallCmd) > 0 {
  167. fmt.Println()
  168. fmt.Println(" " + dep.InstallMsg)
  169. fmt.Printf(" Fix command: %s\n", strings.Join(dep.InstallCmd, " "))
  170. if promptUser("Do you want to run this command?") {
  171. fmt.Println("Running command...")
  172. cmd := exec.Command(dep.InstallCmd[0], dep.InstallCmd[1:]...)
  173. cmd.Stdout = os.Stdout
  174. cmd.Stderr = os.Stderr
  175. cmd.Stdin = os.Stdin
  176. if err := cmd.Run(); err != nil {
  177. fmt.Printf("Command failed: %v\n", err)
  178. os.Exit(1)
  179. }
  180. fmt.Println("✅ Command completed. Please run this check again.")
  181. } else {
  182. fmt.Printf(" Please run manually: %s\n", strings.Join(dep.InstallCmd, " "))
  183. }
  184. } else {
  185. fmt.Println(" " + dep.InstallMsg)
  186. }
  187. }
  188. }
  189. }
  190. // Check for iPhone simulators
  191. fmt.Println()
  192. fmt.Println("Checking for iPhone simulator devices...")
  193. if !checkCommand([]string{"xcrun", "simctl", "list", "devices"}) {
  194. fmt.Println("❌ Cannot check for iPhone simulators")
  195. hasErrors = true
  196. } else {
  197. out, err := exec.Command("xcrun", "simctl", "list", "devices").Output()
  198. if err != nil {
  199. fmt.Println("❌ Failed to list simulator devices")
  200. hasErrors = true
  201. } else if !strings.Contains(string(out), "iPhone") {
  202. fmt.Println("⚠️ No iPhone simulator devices found")
  203. fmt.Println()
  204. // Get the latest iOS runtime
  205. runtimeOut, err := exec.Command("xcrun", "simctl", "list", "runtimes").Output()
  206. if err != nil {
  207. fmt.Println(" Failed to get iOS runtimes:", err)
  208. } else {
  209. lines := strings.Split(string(runtimeOut), "\n")
  210. var latestRuntime string
  211. for _, line := range lines {
  212. if strings.Contains(line, "iOS") && !strings.Contains(line, "unavailable") {
  213. // Extract runtime identifier
  214. parts := strings.Fields(line)
  215. if len(parts) > 0 {
  216. latestRuntime = parts[len(parts)-1]
  217. }
  218. }
  219. }
  220. if latestRuntime == "" {
  221. fmt.Println(" No iOS runtime found. Please install iOS simulators in Xcode:")
  222. fmt.Println(" Xcode → Settings → Platforms → iOS")
  223. } else {
  224. fmt.Println(" Would you like to create an iPhone 15 Pro simulator?")
  225. createCmd := []string{"xcrun", "simctl", "create", "iPhone 15 Pro", "iPhone 15 Pro", latestRuntime}
  226. fmt.Printf(" Command: %s\n", strings.Join(createCmd, " "))
  227. if promptUser("Create simulator?") {
  228. cmd := exec.Command(createCmd[0], createCmd[1:]...)
  229. cmd.Stdout = os.Stdout
  230. cmd.Stderr = os.Stderr
  231. if err := cmd.Run(); err != nil {
  232. fmt.Printf(" Failed to create simulator: %v\n", err)
  233. } else {
  234. fmt.Println(" ✅ iPhone 15 Pro simulator created")
  235. }
  236. } else {
  237. fmt.Println(" Skipping simulator creation")
  238. fmt.Printf(" Create manually: %s\n", strings.Join(createCmd, " "))
  239. }
  240. }
  241. }
  242. } else {
  243. // Count iPhone devices
  244. count := 0
  245. lines := strings.Split(string(out), "\n")
  246. for _, line := range lines {
  247. if strings.Contains(line, "iPhone") && !strings.Contains(line, "unavailable") {
  248. count++
  249. }
  250. }
  251. fmt.Printf("✅ %d iPhone simulator device(s) available\n", count)
  252. }
  253. }
  254. // Final summary
  255. fmt.Println()
  256. fmt.Println("=" + strings.Repeat("=", 50))
  257. if hasErrors {
  258. fmt.Println("❌ Some required dependencies are missing or misconfigured.")
  259. fmt.Println()
  260. fmt.Println("Quick setup guide:")
  261. fmt.Println("1. Install Xcode from Mac App Store (if not installed)")
  262. fmt.Println("2. Open Xcode once and agree to the license")
  263. fmt.Println("3. Install additional components when prompted")
  264. fmt.Println("4. Run: sudo xcode-select -s /Applications/Xcode.app/Contents/Developer")
  265. fmt.Println("5. Download iOS simulators: Xcode → Settings → Platforms → iOS")
  266. fmt.Println("6. Run this check again")
  267. os.Exit(1)
  268. } else {
  269. fmt.Println("✅ All required dependencies are installed!")
  270. fmt.Println(" You're ready for iOS development with Wails!")
  271. }
  272. }
  273. func checkCommand(args []string) bool {
  274. if len(args) == 0 {
  275. return false
  276. }
  277. cmd := exec.Command(args[0], args[1:]...)
  278. cmd.Stdout = nil
  279. cmd.Stderr = nil
  280. err := cmd.Run()
  281. return err == nil
  282. }
  283. func promptUser(question string) bool {
  284. // Check if we're in a non-interactive environment
  285. if os.Getenv("CI") != "" || os.Getenv("TASK_FORCE_YES") == "true" {
  286. fmt.Printf("%s [y/N]: y (auto-accepted)\n", question)
  287. return true
  288. }
  289. reader := bufio.NewReader(os.Stdin)
  290. fmt.Printf("%s [y/N]: ", question)
  291. response, err := reader.ReadString('\n')
  292. if err != nil {
  293. return false
  294. }
  295. response = strings.ToLower(strings.TrimSpace(response))
  296. return response == "y" || response == "yes"
  297. }