finish.nim 11 KB

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