# -------------- post unzip steps ---------------------------------------------
import strutils, os, osproc, streams, browsers
const
arch = $(sizeof(int)*8)
mingw = "mingw$1-6.3.0.7z" % arch
url = r"https://nim-lang.org/download/" & mingw
type
DownloadResult = enum
Failure,
Manual,
Success
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
openDefaultBrowser(url)
result = Manual
else:
if unzip(): result = Success
else:
openDefaultBrowser(url)
result = Manual
when defined(windows):
import registry
proc askBool(m: string): bool =
stdout.write m
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 & "] "
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)
let x = if e.contains(Whitespace): "\"" & e & "\"" else: e
if p.len > 0:
p.add ";"
p.add x
else:
p = x
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 = expandFilename(d / "bin")
if checkGccArch(x): return x
else: incompat.add x
elif dirExists(d & bits):
let x = expandFilename((d & bits) / "bin")
if checkGccArch(x): return x
else: incompat.add x
proc main() =
when defined(windows):
let nimDesiredPath = expandFilename(getCurrentDir() / "bin")
let nimbleDesiredPath = expandFilename(getEnv("USERPROFILE") / ".nimble" / "bin")
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: ""
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:
main()