finish.nim 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  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) & " --out " & "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. let x = if e.contains(Whitespace): "\"" & e & "\"" else: e
  113. if p.len > 0:
  114. p.add ";"
  115. p.add x
  116. else:
  117. p = x
  118. setUnicodeValue(r"Environment", "Path", p, HKEY_CURRENT_USER)
  119. proc createShortcut(src, dest: string; icon = "") =
  120. var cmd = "bin\\makelink.exe \"" & src & "\" \"\" \"" & dest &
  121. ".lnk\" \"\" 1 \"" & splitFile(src).dir & "\""
  122. if icon.len != 0:
  123. cmd.add " \"" & icon & "\" 0"
  124. discard execShellCmd(cmd)
  125. proc createStartMenuEntry*(override = false) =
  126. let appdata = getEnv("APPDATA")
  127. if appdata.len == 0: return
  128. let dest = appdata & r"\Microsoft\Windows\Start Menu\Programs\Nim-" &
  129. NimVersion
  130. if dirExists(dest): return
  131. if override or askBool("Would like to add Nim-" & NimVersion &
  132. " to your start menu? (y/n) "):
  133. createDir(dest)
  134. createShortcut(getCurrentDir() / "tools" / "start.bat", dest / "Nim",
  135. getCurrentDir() / r"icons\nim.ico")
  136. if fileExists("doc/overview.html"):
  137. createShortcut(getCurrentDir() / "doc" / "html" / "overview.html",
  138. dest / "Overview")
  139. if dirExists(r"dist\aporia-0.4.0"):
  140. createShortcut(getCurrentDir() / r"dist\aporia-0.4.0\bin\aporia.exe",
  141. dest / "Aporia")
  142. proc checkGccArch(mingw: string): bool =
  143. let gccExe = mingw / r"gcc.exe"
  144. if fileExists(gccExe):
  145. const nimCompat = "nim_compat.c"
  146. writeFile(nimCompat, """typedef int
  147. Nim_and_C_compiler_disagree_on_target_architecture[
  148. $# == sizeof(void*) ? 1 : -1];
  149. """ % $sizeof(int))
  150. try:
  151. let p = startProcess(gccExe, "", ["-c", nimCompat], nil,
  152. {poStdErrToStdOut, poUsePath})
  153. #echo p.outputStream.readAll()
  154. result = p.waitForExit() == 0
  155. except OSError, IOError:
  156. result = false
  157. finally:
  158. removeFile(nimCompat)
  159. removeFile(nimCompat.changeFileExt("o"))
  160. proc defaultMingwLocations(): seq[string] =
  161. proc probeDir(dir: string; result: var seq[string]) =
  162. for k, x in walkDir(dir, relative=true):
  163. if k in {pcDir, pcLinkToDir}:
  164. if x.contains("mingw") or x.contains("posix"):
  165. let dest = dir / x
  166. probeDir(dest, result)
  167. result.add(dest)
  168. result = @["dist/mingw", "../mingw", r"C:\mingw"]
  169. let pfx86 = getEnv("programfiles(x86)")
  170. let pf = getEnv("programfiles")
  171. when hostCPU == "i386":
  172. probeDir(pfx86, result)
  173. probeDir(pf, result)
  174. else:
  175. probeDir(pf, result)
  176. probeDir(pfx86, result)
  177. proc tryDirs(incompat: var seq[string]; dirs: varargs[string]): string =
  178. let bits = $(sizeof(pointer)*8)
  179. for d in dirs:
  180. if dirExists d:
  181. let x = expand(d / "bin")
  182. if x.len != 0:
  183. if checkGccArch(x): return x
  184. else: incompat.add x
  185. elif dirExists(d & bits):
  186. let x = expand((d & bits) / "bin")
  187. if x.len != 0:
  188. if checkGccArch(x): return x
  189. else: incompat.add x
  190. proc main() =
  191. when defined(windows):
  192. let nimDesiredPath = expand(getCurrentDir() / "bin")
  193. let nimbleBin = getEnv("USERPROFILE") / ".nimble" / "bin"
  194. let nimbleDesiredPath = expand(nimbleBin)
  195. let p = tryGetUnicodeValue(r"Environment", "Path",
  196. HKEY_CURRENT_USER) & ";" & tryGetUnicodeValue(
  197. r"System\CurrentControlSet\Control\Session Manager\Environment", "Path",
  198. HKEY_LOCAL_MACHINE)
  199. var nimAlreadyInPath = false
  200. var nimbleAlreadyInPath = false
  201. var mingWchoices: seq[string] = @[]
  202. var incompat: seq[string] = @[]
  203. for x in p.split(';'):
  204. if x.len == 0: continue
  205. let y = try: expandFilename(if x[0] == '"' and x[^1] == '"':
  206. substr(x, 1, x.len-2) else: x)
  207. except OSError as e:
  208. if e.errorCode == 0: x else: ""
  209. except: ""
  210. if y.cmpIgnoreCase(nimDesiredPath) == 0:
  211. nimAlreadyInPath = true
  212. elif y.cmpIgnoreCase(nimbleDesiredPath) == 0:
  213. nimbleAlreadyInPath = true
  214. elif y.toLowerAscii.contains("mingw"):
  215. if dirExists(y):
  216. if checkGccArch(y): mingWchoices.add y
  217. else: incompat.add y
  218. if nimAlreadyInPath:
  219. echo "bin\\nim.exe is already in your PATH [Skipping]"
  220. else:
  221. if askBool("nim.exe is not in your PATH environment variable.\n" &
  222. "Should it be added permanently? (y/n) "):
  223. addToPathEnv(nimDesiredPath)
  224. if nimbleAlreadyInPath:
  225. echo nimbleDesiredPath & " is already in your PATH [Skipping]"
  226. else:
  227. if askBool(nimbleDesiredPath & " is not in your PATH environment variable.\n" &
  228. "Should it be added permanently? (y/n) "):
  229. addToPathEnv(nimbleDesiredPath)
  230. if mingWchoices.len == 0:
  231. # No mingw in path, so try a few locations:
  232. let alternative = tryDirs(incompat, defaultMingwLocations())
  233. if alternative.len == 0:
  234. if incompat.len > 0:
  235. echo "The following *incompatible* MingW installations exist"
  236. for x in incompat: echo x
  237. echo "*incompatible* means Nim and GCC disagree on the size of a pointer."
  238. echo "No compatible MingW candidates found " &
  239. "in the standard locations [Error]"
  240. if askBool("Do you want to download MingW from Nim's website? (y/n) "):
  241. let dest = getCurrentDir() / "dist"
  242. var retry = false
  243. case downloadMingw()
  244. of Manual:
  245. echo "After download, move it to: ", dest
  246. if askBool("Download successful? (y/n) "):
  247. while not fileExists("dist" / mingw):
  248. echo "could not find: ", "dist" / mingw
  249. if not askBool("Try again? (y/n) "): break
  250. if unzip(): retry = true
  251. of Failure: discard
  252. of Success:
  253. retry = true
  254. if retry:
  255. incompat.setLen 0
  256. let alternative = tryDirs(incompat, defaultMingwLocations())
  257. if alternative.len == 0:
  258. if incompat.len > 0:
  259. echo "The following *incompatible* MingW installations exist"
  260. for x in incompat: echo x
  261. echo "*incompatible* means Nim and GCC disagree on the size of a pointer."
  262. echo "Still no compatible MingW candidates found " &
  263. "in the standard locations [Error]"
  264. else:
  265. echo "Patching Nim's config to use:"
  266. echo alternative
  267. patchConfig(alternative)
  268. else:
  269. if askBool("Found a MingW directory that is not in your PATH.\n" &
  270. alternative &
  271. "\nShould it be added to your PATH permanently? (y/n) "):
  272. addToPathEnv(alternative)
  273. elif askBool("Do you want to patch Nim's config to use this? (y/n) "):
  274. patchConfig(alternative)
  275. elif mingWchoices.len == 1:
  276. if askBool("MingW installation found at " & mingWchoices[0] & "\n" &
  277. "Do you want to patch Nim's config to use this?\n" &
  278. "(Not required since it's in your PATH!) (y/n) "):
  279. patchConfig(mingWchoices[0])
  280. else:
  281. echo "Multiple MingW installations found: "
  282. for i in 0..high(mingWchoices):
  283. echo "[", i, "] ", mingWchoices[i]
  284. if askBool("Do you want to patch Nim's config to use one of these? (y/n) "):
  285. let idx = askNumber("Which one do you want to use for Nim? ",
  286. 1, len(mingWchoices))
  287. patchConfig(mingWchoices[idx-1])
  288. createStartMenuEntry()
  289. else:
  290. echo("Add ", getCurrentDir(), "/bin to your PATH...")
  291. when isMainModule:
  292. when defined(testdownload):
  293. discard downloadMingw()
  294. else:
  295. if "-y" in commandLineParams():
  296. interactive = false
  297. main()