summary refs log tree commit diff stats
path: root/tools/finish.nim
diff options
context:
space:
mode:
Diffstat (limited to 'tools/finish.nim')
-rw-r--r--tools/finish.nim321
1 files changed, 321 insertions, 0 deletions
diff --git a/tools/finish.nim b/tools/finish.nim
new file mode 100644
index 000000000..69838829c
--- /dev/null
+++ b/tools/finish.nim
@@ -0,0 +1,321 @@
+
+# -------------- post unzip steps ---------------------------------------------
+
+import strutils, os, osproc, streams, browsers
+
+const
+  arch = $(sizeof(int)*8)
+  mingw = "mingw$1.7z" % arch
+  url = r"https://nim-lang.org/download/" & mingw
+
+var
+  interactive = true
+
+type
+  DownloadResult = enum
+    Failure,
+    Manual,
+    Success
+
+proc expand(s: string): string =
+  try:
+    result = expandFilename(s)
+  except OSError, IOError:
+    result = s
+
+proc unzip(): bool =
+  if not fileExists("dist" / mingw):
+    echo "Could not find ", "dist" / mingw
+    return false
+  try:
+    let p = osproc.startProcess(r"bin\7zG.exe", getCurrentDir() / r"dist",
+                                ["x", mingw])
+    if p.waitForExit != 0:
+      echo "Unpacking failed: " & mingw
+    else:
+      result = true
+  except:
+    result = false
+
+proc downloadMingw(): DownloadResult =
+  let curl = findExe"curl"
+  var cmd: string
+  if curl.len > 0:
+    cmd = quoteShell(curl) & " --output " & "dist" / mingw & " " & url
+  elif fileExists"bin/nimgrab.exe":
+    cmd = r"bin\nimgrab.exe " & url & " dist" / mingw
+  if cmd.len > 0:
+    if execShellCmd(cmd) != 0:
+      echo "download failed! ", cmd
+      if interactive:
+        openDefaultBrowser(url)
+        result = Manual
+      else:
+        result = Failure
+    else:
+      if unzip(): result = Success
+  else:
+    if interactive:
+      openDefaultBrowser(url)
+      result = Manual
+    else:
+      result = Failure
+
+when defined(windows):
+  import registry
+
+  proc askBool(m: string): bool =
+    stdout.write m
+    if not interactive:
+      stdout.writeLine "y (non-interactive mode)"
+      return true
+    while true:
+      try:
+        let answer = stdin.readLine().normalize
+        case answer
+        of "y", "yes":
+          return true
+        of "n", "no":
+          return false
+        else:
+          echo "Please type 'y' or 'n'"
+      except EOFError:
+        quit(1)
+
+  proc askNumber(m: string; a, b: int): int =
+    stdout.write m
+    stdout.write " [" & $a & ".." & $b & "] "
+    if not interactive:
+      stdout.writeLine $a & " (non-interactive mode)"
+      return a
+    while true:
+      let answer = stdin.readLine()
+      try:
+        result = parseInt answer
+        if result < a or result > b:
+          raise newException(ValueError, "number out of range")
+        break
+      except ValueError:
+        echo "Please type in a number between ", a, " and ", b
+
+  proc patchConfig(mingw: string) =
+    const
+      cfgFile = "config/nim.cfg"
+      lookFor = """#gcc.path = r"$nim\dist\mingw\bin""""
+      replacePattern = """gcc.path = r"$1""""
+    try:
+      let cfg = readFile(cfgFile)
+      let newCfg = cfg.replace(lookFor, replacePattern % mingw)
+      if newCfg == cfg:
+        echo "Could not patch 'config/nim.cfg' [Error]"
+        echo "Reason: patch substring not found:"
+        echo lookFor
+      else:
+        writeFile(cfgFile, newCfg)
+    except IOError:
+      echo "Could not access 'config/nim.cfg' [Error]"
+
+  proc tryGetUnicodeValue(path, key: string; handle: HKEY): string =
+    try:
+      result = getUnicodeValue(path, key, handle)
+    except:
+      result = ""
+
+  proc addToPathEnv*(e: string) =
+    var p = tryGetUnicodeValue(r"Environment", "Path", HKEY_CURRENT_USER)
+    if p.len > 0:
+      p.add ";"
+      p.add e
+    else:
+      p = e
+    setUnicodeValue(r"Environment", "Path", p, HKEY_CURRENT_USER)
+
+  proc createShortcut(src, dest: string; icon = "") =
+    var cmd = "bin\\makelink.exe \"" & src & "\" \"\" \"" & dest &
+         ".lnk\" \"\" 1 \"" & splitFile(src).dir & "\""
+    if icon.len != 0:
+      cmd.add " \"" & icon & "\" 0"
+    discard execShellCmd(cmd)
+
+  proc createStartMenuEntry*(override = false) =
+    let appdata = getEnv("APPDATA")
+    if appdata.len == 0: return
+    let dest = appdata & r"\Microsoft\Windows\Start Menu\Programs\Nim-" &
+               NimVersion
+    if dirExists(dest): return
+    if override or askBool("Would like to add Nim-" & NimVersion &
+               " to your start menu? (y/n) "):
+      createDir(dest)
+      createShortcut(getCurrentDir() / "tools" / "start.bat", dest / "Nim",
+                     getCurrentDir() / r"icons\nim.ico")
+      if fileExists("doc/overview.html"):
+        createShortcut(getCurrentDir() / "doc" / "html" / "overview.html",
+                       dest / "Overview")
+      if dirExists(r"dist\aporia-0.4.0"):
+        createShortcut(getCurrentDir() / r"dist\aporia-0.4.0\bin\aporia.exe",
+                       dest / "Aporia")
+
+  proc checkGccArch(mingw: string): bool =
+    let gccExe = mingw / r"gcc.exe"
+    if fileExists(gccExe):
+      const nimCompat = "nim_compat.c"
+      writeFile(nimCompat, """typedef int
+        Nim_and_C_compiler_disagree_on_target_architecture[
+          $# == sizeof(void*) ? 1 : -1];
+      """ % $sizeof(int))
+      try:
+        let p = startProcess(gccExe, "", ["-c", nimCompat], nil,
+                            {poStdErrToStdOut, poUsePath})
+        #echo p.outputStream.readAll()
+        result = p.waitForExit() == 0
+      except OSError, IOError:
+        result = false
+      finally:
+        removeFile(nimCompat)
+        removeFile(nimCompat.changeFileExt("o"))
+
+  proc defaultMingwLocations(): seq[string] =
+    proc probeDir(dir: string; result: var seq[string]) =
+      for k, x in walkDir(dir, relative=true):
+        if k in {pcDir, pcLinkToDir}:
+          if x.contains("mingw") or x.contains("posix"):
+            let dest = dir / x
+            probeDir(dest, result)
+            result.add(dest)
+
+    result = @["dist/mingw", "../mingw", r"C:\mingw"]
+    let pfx86 = getEnv("programfiles(x86)")
+    let pf = getEnv("programfiles")
+    when hostCPU == "i386":
+      probeDir(pfx86, result)
+      probeDir(pf, result)
+    else:
+      probeDir(pf, result)
+      probeDir(pfx86, result)
+
+  proc tryDirs(incompat: var seq[string]; dirs: varargs[string]): string =
+    let bits = $(sizeof(pointer)*8)
+    for d in dirs:
+      if dirExists d:
+        let x = expand(d / "bin")
+        if x.len != 0:
+          if checkGccArch(x): return x
+          else: incompat.add x
+      elif dirExists(d & bits):
+        let x = expand((d & bits) / "bin")
+        if x.len != 0:
+          if checkGccArch(x): return x
+          else: incompat.add x
+
+proc main() =
+  when defined(windows):
+    let nimDesiredPath = expand(getCurrentDir() / "bin")
+    let nimbleBin = getEnv("USERPROFILE") / ".nimble" / "bin"
+    let nimbleDesiredPath = expand(nimbleBin)
+    let p = tryGetUnicodeValue(r"Environment", "Path",
+      HKEY_CURRENT_USER) & ";" & tryGetUnicodeValue(
+        r"System\CurrentControlSet\Control\Session Manager\Environment", "Path",
+        HKEY_LOCAL_MACHINE)
+    var nimAlreadyInPath = false
+    var nimbleAlreadyInPath = false
+    var mingWchoices: seq[string] = @[]
+    var incompat: seq[string] = @[]
+    for x in p.split(';'):
+      if x.len == 0: continue
+      let y = try: expandFilename(if x[0] == '"' and x[^1] == '"':
+                                    substr(x, 1, x.len-2) else: x)
+              except OSError as e:
+                if e.errorCode == 0: x else: ""
+              except: ""
+      if y.cmpIgnoreCase(nimDesiredPath) == 0:
+        nimAlreadyInPath = true
+      elif y.cmpIgnoreCase(nimbleDesiredPath) == 0:
+        nimbleAlreadyInPath = true
+      elif y.toLowerAscii.contains("mingw"):
+        if dirExists(y):
+          if checkGccArch(y): mingWchoices.add y
+          else: incompat.add y
+
+    if nimAlreadyInPath:
+      echo "bin\\nim.exe is already in your PATH [Skipping]"
+    else:
+      if askBool("nim.exe is not in your PATH environment variable.\n" &
+          "Should it be added permanently? (y/n) "):
+        addToPathEnv(nimDesiredPath)
+
+    if nimbleAlreadyInPath:
+      echo nimbleDesiredPath & " is already in your PATH [Skipping]"
+    else:
+      if askBool(nimbleDesiredPath & " is not in your PATH environment variable.\n" &
+          "Should it be added permanently? (y/n) "):
+        addToPathEnv(nimbleDesiredPath)
+
+    if mingWchoices.len == 0:
+      # No mingw in path, so try a few locations:
+      let alternative = tryDirs(incompat, defaultMingwLocations())
+      if alternative.len == 0:
+        if incompat.len > 0:
+          echo "The following *incompatible* MingW installations exist"
+          for x in incompat: echo x
+          echo "*incompatible* means Nim and GCC disagree on the size of a pointer."
+        echo "No compatible MingW candidates found " &
+             "in the standard locations [Error]"
+        if askBool("Do you want to download MingW from Nim's website? (y/n) "):
+          let dest = getCurrentDir() / "dist"
+          var retry = false
+          case downloadMingw()
+          of Manual:
+            echo "After download, move it to: ", dest
+            if askBool("Download successful? (y/n) "):
+              while not fileExists("dist" / mingw):
+                echo "could not find: ", "dist" / mingw
+                if not askBool("Try again? (y/n) "): break
+              if unzip(): retry = true
+          of Failure: discard
+          of Success:
+            retry = true
+          if retry:
+            incompat.setLen 0
+            let alternative = tryDirs(incompat, defaultMingwLocations())
+            if alternative.len == 0:
+              if incompat.len > 0:
+                echo "The following *incompatible* MingW installations exist"
+                for x in incompat: echo x
+                echo "*incompatible* means Nim and GCC disagree on the size of a pointer."
+              echo "Still no compatible MingW candidates found " &
+                   "in the standard locations [Error]"
+            else:
+              echo "Patching Nim's config to use:"
+              echo alternative
+              patchConfig(alternative)
+      else:
+        if askBool("Found a MingW directory that is not in your PATH.\n" &
+                   alternative &
+                   "\nShould it be added to your PATH permanently? (y/n) "):
+          addToPathEnv(alternative)
+        elif askBool("Do you want to patch Nim's config to use this? (y/n) "):
+          patchConfig(alternative)
+    elif mingWchoices.len == 1:
+      if askBool("MingW installation found at " & mingWchoices[0] & "\n" &
+         "Do you want to patch Nim's config to use this?\n" &
+         "(Not required since it's in your PATH!) (y/n) "):
+        patchConfig(mingWchoices[0])
+    else:
+      echo "Multiple MingW installations found: "
+      for i in 0..high(mingWchoices):
+        echo "[", i, "] ", mingWchoices[i]
+      if askBool("Do you want to patch Nim's config to use one of these? (y/n) "):
+        let idx = askNumber("Which one do you want to use for Nim? ",
+            1, len(mingWchoices))
+        patchConfig(mingWchoices[idx-1])
+    createStartMenuEntry()
+  else:
+    echo("Add ", getCurrentDir(), "/bin to your PATH...")
+
+when isMainModule:
+  when defined(testdownload):
+    discard downloadMingw()
+  else:
+    if "-y" in commandLineParams():
+      interactive = false
+    main()