finish.nim 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. # -------------- post unzip steps ---------------------------------------------
  2. import strutils, os, osproc, streams, browsers
  3. const
  4. arch = $(sizeof(int)*8)
  5. mingw = "mingw$1-6.3.0.7z" % arch
  6. url = r"https://nim-lang.org/download/" & mingw
  7. var
  8. interactive = true
  9. type
  10. DownloadResult = enum
  11. Failure,
  12. Manual,
  13. Success
  14. proc unzip(): bool =
  15. if not fileExists("dist" / mingw):
  16. echo "Could not find ", "dist" / mingw
  17. return false
  18. try:
  19. let p = osproc.startProcess(r"bin\7zG.exe", getCurrentDir() / r"dist",
  20. ["x", mingw])
  21. if p.waitForExit != 0:
  22. echo "Unpacking failed: " & mingw
  23. else:
  24. result = true
  25. except:
  26. result = false
  27. proc downloadMingw(): DownloadResult =
  28. let curl = findExe"curl"
  29. var cmd: string
  30. if curl.len > 0:
  31. cmd = quoteShell(curl) & " --out " & "dist" / mingw & " " & url
  32. elif fileExists"bin/nimgrab.exe":
  33. cmd = r"bin\nimgrab.exe " & url & " dist" / mingw
  34. if cmd.len > 0:
  35. if execShellCmd(cmd) != 0:
  36. echo "download failed! ", cmd
  37. if interactive:
  38. openDefaultBrowser(url)
  39. result = Manual
  40. else:
  41. result = Failure
  42. else:
  43. if unzip(): result = Success
  44. else:
  45. if interactive:
  46. openDefaultBrowser(url)
  47. result = Manual
  48. else:
  49. result = Failure
  50. when defined(windows):
  51. import registry
  52. proc askBool(m: string): bool =
  53. stdout.write m
  54. if not interactive:
  55. stdout.writeLine "y (non-interactive mode)"
  56. return true
  57. while true:
  58. try:
  59. let answer = stdin.readLine().normalize
  60. case answer
  61. of "y", "yes":
  62. return true
  63. of "n", "no":
  64. return false
  65. else:
  66. echo "Please type 'y' or 'n'"
  67. except EOFError:
  68. quit(1)
  69. proc askNumber(m: string; a, b: int): int =
  70. stdout.write m
  71. stdout.write " [" & $a & ".." & $b & "] "
  72. if not interactive:
  73. stdout.writeLine $a & " (non-interactive mode)"
  74. return a
  75. while true:
  76. let answer = stdin.readLine()
  77. try:
  78. result = parseInt answer
  79. if result < a or result > b:
  80. raise newException(ValueError, "number out of range")
  81. break
  82. except ValueError:
  83. echo "Please type in a number between ", a, " and ", b
  84. proc patchConfig(mingw: string) =
  85. const
  86. cfgFile = "config/nim.cfg"
  87. lookFor = """#gcc.path = r"$nim\dist\mingw\bin""""
  88. replacePattern = """gcc.path = r"$1""""
  89. try:
  90. let cfg = readFile(cfgFile)
  91. let newCfg = cfg.replace(lookFor, replacePattern % mingw)
  92. if newCfg == cfg:
  93. echo "Could not patch 'config/nim.cfg' [Error]"
  94. echo "Reason: patch substring not found:"
  95. echo lookFor
  96. else:
  97. writeFile(cfgFile, newCfg)
  98. except IOError:
  99. echo "Could not access 'config/nim.cfg' [Error]"
  100. proc tryGetUnicodeValue(path, key: string; handle: HKEY): string =
  101. try:
  102. result = getUnicodeValue(path, key, handle)
  103. except:
  104. result = ""
  105. proc addToPathEnv*(e: string) =
  106. var p = tryGetUnicodeValue(r"Environment", "Path", HKEY_CURRENT_USER)
  107. let x = if e.contains(Whitespace): "\"" & e & "\"" else: e
  108. if p.len > 0:
  109. p.add ";"
  110. p.add x
  111. else:
  112. p = x
  113. setUnicodeValue(r"Environment", "Path", p, HKEY_CURRENT_USER)
  114. proc createShortcut(src, dest: string; icon = "") =
  115. var cmd = "bin\\makelink.exe \"" & src & "\" \"\" \"" & dest &
  116. ".lnk\" \"\" 1 \"" & splitFile(src).dir & "\""
  117. if icon.len != 0:
  118. cmd.add " \"" & icon & "\" 0"
  119. discard execShellCmd(cmd)
  120. proc createStartMenuEntry*(override = false) =
  121. let appdata = getEnv("APPDATA")
  122. if appdata.len == 0: return
  123. let dest = appdata & r"\Microsoft\Windows\Start Menu\Programs\Nim-" &
  124. NimVersion
  125. if dirExists(dest): return
  126. if override or askBool("Would like to add Nim-" & NimVersion &
  127. " to your start menu? (y/n) "):
  128. createDir(dest)
  129. createShortcut(getCurrentDir() / "tools" / "start.bat", dest / "Nim",
  130. getCurrentDir() / r"icons\nim.ico")
  131. if fileExists("doc/overview.html"):
  132. createShortcut(getCurrentDir() / "doc" / "html" / "overview.html",
  133. dest / "Overview")
  134. if dirExists(r"dist\aporia-0.4.0"):
  135. createShortcut(getCurrentDir() / r"dist\aporia-0.4.0\bin\aporia.exe",
  136. dest / "Aporia")
  137. proc checkGccArch(mingw: string): bool =
  138. let gccExe = mingw / r"gcc.exe"
  139. if fileExists(gccExe):
  140. const nimCompat = "nim_compat.c"
  141. writeFile(nimCompat, """typedef int
  142. Nim_and_C_compiler_disagree_on_target_architecture[
  143. $# == sizeof(void*) ? 1 : -1];
  144. """ % $sizeof(int))
  145. try:
  146. let p = startProcess(gccExe, "", ["-c", nimCompat], nil,
  147. {poStdErrToStdOut, poUsePath})
  148. #echo p.outputStream.readAll()
  149. result = p.waitForExit() == 0
  150. except OSError, IOError:
  151. result = false
  152. finally:
  153. removeFile(nimCompat)
  154. removeFile(nimCompat.changeFileExt("o"))
  155. proc defaultMingwLocations(): seq[string] =
  156. proc probeDir(dir: string; result: var seq[string]) =
  157. for k, x in walkDir(dir, relative=true):
  158. if k in {pcDir, pcLinkToDir}:
  159. if x.contains("mingw") or x.contains("posix"):
  160. let dest = dir / x
  161. probeDir(dest, result)
  162. result.add(dest)
  163. result = @["dist/mingw", "../mingw", r"C:\mingw"]
  164. let pfx86 = getEnv("programfiles(x86)")
  165. let pf = getEnv("programfiles")
  166. when hostCPU == "i386":
  167. probeDir(pfx86, result)
  168. probeDir(pf, result)
  169. else:
  170. probeDir(pf, result)
  171. probeDir(pfx86, result)
  172. proc tryDirs(incompat: var seq[string]; dirs: varargs[string]): string =
  173. let bits = $(sizeof(pointer)*8)
  174. for d in dirs:
  175. if dirExists d:
  176. let x = expandFilename(d / "bin")
  177. if checkGccArch(x): return x
  178. else: incompat.add x
  179. elif dirExists(d & bits):
  180. let x = expandFilename((d & bits) / "bin")
  181. if checkGccArch(x): return x
  182. else: incompat.add x
  183. proc main() =
  184. when defined(windows):
  185. let nimDesiredPath = expandFilename(getCurrentDir() / "bin")
  186. let nimbleDesiredPath = expandFilename(getEnv("USERPROFILE") / ".nimble" / "bin")
  187. let p = tryGetUnicodeValue(r"Environment", "Path",
  188. HKEY_CURRENT_USER) & ";" & tryGetUnicodeValue(
  189. r"System\CurrentControlSet\Control\Session Manager\Environment", "Path",
  190. HKEY_LOCAL_MACHINE)
  191. var nimAlreadyInPath = false
  192. var nimbleAlreadyInPath = false
  193. var mingWchoices: seq[string] = @[]
  194. var incompat: seq[string] = @[]
  195. for x in p.split(';'):
  196. if x.len == 0: continue
  197. let y = try: expandFilename(if x[0] == '"' and x[^1] == '"':
  198. substr(x, 1, x.len-2) else: x)
  199. except: ""
  200. if y.cmpIgnoreCase(nimDesiredPath) == 0:
  201. nimAlreadyInPath = true
  202. elif y.cmpIgnoreCase(nimbleDesiredPath) == 0:
  203. nimbleAlreadyInPath = true
  204. elif y.toLowerAscii.contains("mingw"):
  205. if dirExists(y):
  206. if checkGccArch(y): mingWchoices.add y
  207. else: incompat.add y
  208. if nimAlreadyInPath:
  209. echo "bin\\nim.exe is already in your PATH [Skipping]"
  210. else:
  211. if askBool("nim.exe is not in your PATH environment variable.\n" &
  212. "Should it be added permanently? (y/n) "):
  213. addToPathEnv(nimDesiredPath)
  214. if nimbleAlreadyInPath:
  215. echo nimbleDesiredPath & " is already in your PATH [Skipping]"
  216. else:
  217. if askBool(nimbleDesiredPath & " is not in your PATH environment variable.\n" &
  218. "Should it be added permanently? (y/n) "):
  219. addToPathEnv(nimbleDesiredPath)
  220. if mingWchoices.len == 0:
  221. # No mingw in path, so try a few locations:
  222. let alternative = tryDirs(incompat, defaultMingwLocations())
  223. if alternative.len == 0:
  224. if incompat.len > 0:
  225. echo "The following *incompatible* MingW installations exist"
  226. for x in incompat: echo x
  227. echo "*incompatible* means Nim and GCC disagree on the size of a pointer."
  228. echo "No compatible MingW candidates found " &
  229. "in the standard locations [Error]"
  230. if askBool("Do you want to download MingW from Nim's website? (y/n) "):
  231. let dest = getCurrentDir() / "dist"
  232. var retry = false
  233. case downloadMingw()
  234. of Manual:
  235. echo "After download, move it to: ", dest
  236. if askBool("Download successful? (y/n) "):
  237. while not fileExists("dist" / mingw):
  238. echo "could not find: ", "dist" / mingw
  239. if not askBool("Try again? (y/n) "): break
  240. if unzip(): retry = true
  241. of Failure: discard
  242. of Success:
  243. retry = true
  244. if retry:
  245. incompat.setLen 0
  246. let alternative = tryDirs(incompat, defaultMingwLocations())
  247. if alternative.len == 0:
  248. if incompat.len > 0:
  249. echo "The following *incompatible* MingW installations exist"
  250. for x in incompat: echo x
  251. echo "*incompatible* means Nim and GCC disagree on the size of a pointer."
  252. echo "Still no compatible MingW candidates found " &
  253. "in the standard locations [Error]"
  254. else:
  255. echo "Patching Nim's config to use:"
  256. echo alternative
  257. patchConfig(alternative)
  258. else:
  259. if askBool("Found a MingW directory that is not in your PATH.\n" &
  260. alternative &
  261. "\nShould it be added to your PATH permanently? (y/n) "):
  262. addToPathEnv(alternative)
  263. elif askBool("Do you want to patch Nim's config to use this? (y/n) "):
  264. patchConfig(alternative)
  265. elif mingWchoices.len == 1:
  266. if askBool("MingW installation found at " & mingWchoices[0] & "\n" &
  267. "Do you want to patch Nim's config to use this?\n" &
  268. "(Not required since it's in your PATH!) (y/n) "):
  269. patchConfig(mingWchoices[0])
  270. else:
  271. echo "Multiple MingW installations found: "
  272. for i in 0..high(mingWchoices):
  273. echo "[", i, "] ", mingWchoices[i]
  274. if askBool("Do you want to patch Nim's config to use one of these? (y/n) "):
  275. let idx = askNumber("Which one do you want to use for Nim? ",
  276. 1, len(mingWchoices))
  277. patchConfig(mingWchoices[idx-1])
  278. createStartMenuEntry()
  279. else:
  280. echo("Add ", getCurrentDir(), "/bin to your PATH...")
  281. when isMainModule:
  282. when defined(testdownload):
  283. discard downloadMingw()
  284. else:
  285. if "-y" in commandLineParams():
  286. interactive = false
  287. main()