finish.nim 10 KB

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