# -------------- 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) & " --out " & "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()