koch supports the finish command for smooth installations on Windows
This commit is contained in:
parent
3dc8e593ff
commit
6e6b90278c
3 changed files with 224 additions and 74 deletions
|
|
@ -89,6 +89,7 @@ Files: "bin/c2nim.exe"
|
||||||
Files: "bin/nimgrep.exe"
|
Files: "bin/nimgrep.exe"
|
||||||
Files: "bin/nimsuggest.exe"
|
Files: "bin/nimsuggest.exe"
|
||||||
Files: "bin/nimble.exe"
|
Files: "bin/nimble.exe"
|
||||||
|
Files: "bin/makelink.exe"
|
||||||
Files: "bin/*.dll"
|
Files: "bin/*.dll"
|
||||||
|
|
||||||
Files: "dist/*.dll"
|
Files: "dist/*.dll"
|
||||||
|
|
|
||||||
231
koch.nim
231
koch.nim
|
|
@ -40,7 +40,8 @@ Options:
|
||||||
--help, -h shows this help and quits
|
--help, -h shows this help and quits
|
||||||
Possible Commands:
|
Possible Commands:
|
||||||
boot [options] bootstraps with given command line options
|
boot [options] bootstraps with given command line options
|
||||||
install [bindir] installs to given directory; Unix only!
|
finish setup PATH and check for a valid GCC installation
|
||||||
|
distrohelper [bindir] helper for distro packagers
|
||||||
geninstall generate ./install.sh; Unix only!
|
geninstall generate ./install.sh; Unix only!
|
||||||
testinstall test tar.xz package; Unix only! Only for devs!
|
testinstall test tar.xz package; Unix only! Only for devs!
|
||||||
clean cleans Nim project; removes generated files
|
clean cleans Nim project; removes generated files
|
||||||
|
|
@ -52,8 +53,6 @@ Possible Commands:
|
||||||
xz builds the installation XZ package
|
xz builds the installation XZ package
|
||||||
nsis [options] builds the NSIS Setup installer (for Windows)
|
nsis [options] builds the NSIS Setup installer (for Windows)
|
||||||
tests [options] run the testsuite
|
tests [options] run the testsuite
|
||||||
update updates nim to the latest version from github
|
|
||||||
(compile koch with -d:withUpdate to enable)
|
|
||||||
temp options creates a temporary compiler for testing
|
temp options creates a temporary compiler for testing
|
||||||
winrelease creates a release (for coredevs only)
|
winrelease creates a release (for coredevs only)
|
||||||
Boot options:
|
Boot options:
|
||||||
|
|
@ -211,10 +210,6 @@ proc geninstall(args="") =
|
||||||
exec("$# cc -r $# --var:version=$# --var:mingw=none --main:compiler/nim.nim scripts compiler/installer.ini $#" %
|
exec("$# cc -r $# --var:version=$# --var:mingw=none --main:compiler/nim.nim scripts compiler/installer.ini $#" %
|
||||||
[findNim(), compileNimInst, VersionAsString, args])
|
[findNim(), compileNimInst, VersionAsString, args])
|
||||||
|
|
||||||
proc install(args: string) =
|
|
||||||
geninstall()
|
|
||||||
exec("sh ./install.sh $#" % args)
|
|
||||||
|
|
||||||
proc web(args: string) =
|
proc web(args: string) =
|
||||||
exec("$# js tools/dochack/dochack.nim" % findNim())
|
exec("$# js tools/dochack/dochack.nim" % findNim())
|
||||||
exec("$# cc -r tools/nimweb.nim $# web/website.ini --putenv:nimversion=$#" %
|
exec("$# cc -r tools/nimweb.nim $# web/website.ini --putenv:nimversion=$#" %
|
||||||
|
|
@ -321,67 +316,6 @@ proc clean(args: string) =
|
||||||
echo "removing dir: ", path
|
echo "removing dir: ", path
|
||||||
removeDir(path)
|
removeDir(path)
|
||||||
|
|
||||||
# -------------- update -------------------------------------------------------
|
|
||||||
|
|
||||||
when defined(withUpdate):
|
|
||||||
when defined(windows):
|
|
||||||
{.warning: "Windows users: Make sure to run 'koch update' in Bash.".}
|
|
||||||
|
|
||||||
proc update(args: string) =
|
|
||||||
when defined(windows):
|
|
||||||
echo("Windows users: Make sure to be running this in Bash. ",
|
|
||||||
"If you aren't, press CTRL+C now.")
|
|
||||||
|
|
||||||
var thisDir = getAppDir()
|
|
||||||
var git = findExe("git")
|
|
||||||
echo("Checking for git repo and git executable...")
|
|
||||||
if existsDir(thisDir & "/.git") and git != "":
|
|
||||||
echo("Git repo found!")
|
|
||||||
# use git to download latest source
|
|
||||||
echo("Checking for updates...")
|
|
||||||
discard startCmd(git & " fetch origin master")
|
|
||||||
var procs = startCmd(git & " diff origin/master master")
|
|
||||||
var errcode = procs.waitForExit()
|
|
||||||
var output = readLine(procs.outputStream)
|
|
||||||
echo(output)
|
|
||||||
if errcode == 0:
|
|
||||||
if output == "":
|
|
||||||
# No changes
|
|
||||||
echo("No update. Exiting...")
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
echo("Fetching updates from repo...")
|
|
||||||
var pullout = execCmdEx(git & " pull origin master")
|
|
||||||
if pullout[1] != 0:
|
|
||||||
quit("An error has occurred.")
|
|
||||||
else:
|
|
||||||
if pullout[0].startsWith("Already up-to-date."):
|
|
||||||
quit("No new changes fetched from the repo. " &
|
|
||||||
"Local branch must be ahead of it. Exiting...")
|
|
||||||
else:
|
|
||||||
quit("An error has occurred.")
|
|
||||||
|
|
||||||
else:
|
|
||||||
echo("No repo or executable found!")
|
|
||||||
when defined(haveZipLib):
|
|
||||||
echo("Falling back.. Downloading source code from repo...")
|
|
||||||
# use dom96's httpclient to download zip
|
|
||||||
downloadFile("https://github.com/Araq/Nim/zipball/master",
|
|
||||||
thisDir / "update.zip")
|
|
||||||
try:
|
|
||||||
echo("Extracting source code from archive...")
|
|
||||||
var zip: TZipArchive
|
|
||||||
discard open(zip, thisDir & "/update.zip", fmRead)
|
|
||||||
extractAll(zip, thisDir & "/")
|
|
||||||
except:
|
|
||||||
quit("Error reading archive.")
|
|
||||||
else:
|
|
||||||
quit("No failback available. Exiting...")
|
|
||||||
|
|
||||||
echo("Starting update...")
|
|
||||||
boot(args)
|
|
||||||
echo("Update complete!")
|
|
||||||
|
|
||||||
# -------------- builds a release ---------------------------------------------
|
# -------------- builds a release ---------------------------------------------
|
||||||
|
|
||||||
#[
|
#[
|
||||||
|
|
@ -401,6 +335,159 @@ proc run7z(platform: string, patterns: varargs[string]) =
|
||||||
proc winRelease() =
|
proc winRelease() =
|
||||||
exec(r"call ci\nsis_build.bat " & VersionAsString)
|
exec(r"call ci\nsis_build.bat " & VersionAsString)
|
||||||
|
|
||||||
|
# -------------- post unzip steps ---------------------------------------------
|
||||||
|
|
||||||
|
when defined(windows):
|
||||||
|
import registry
|
||||||
|
|
||||||
|
proc askBool(m: string): bool =
|
||||||
|
stdout.write m
|
||||||
|
while true:
|
||||||
|
let answer = stdin.readLine().normalize
|
||||||
|
case answer
|
||||||
|
of "y", "yes":
|
||||||
|
return true
|
||||||
|
of "n", "no":
|
||||||
|
return false
|
||||||
|
else:
|
||||||
|
echo "Please type 'y' or 'n'"
|
||||||
|
|
||||||
|
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 addToPathEnv(e: string) =
|
||||||
|
let p = getUnicodeValue(r"Environment", "Path", HKEY_CURRENT_USER)
|
||||||
|
let x = if e.contains(Whitespace): "\"" & e & "\"" else: e
|
||||||
|
setUnicodeValue(r"Environment", "Path", p & ";" & x, 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() =
|
||||||
|
let appdata = getEnv("APPDATA")
|
||||||
|
if appdata.len == 0: return
|
||||||
|
let dest = appdata & r"\Microsoft\Windows\Start Menu\Programs\Nim-" &
|
||||||
|
VersionAsString
|
||||||
|
if dirExists(dest): return
|
||||||
|
if askBool("Would like to add Nim-" & VersionAsString &
|
||||||
|
" to your start menu? (y/n) "):
|
||||||
|
createDir(dest)
|
||||||
|
createShortcut(getCurrentDir() / "start.bat", dest / "Nim",
|
||||||
|
getCurrentDir() / r"icons\nim.ico")
|
||||||
|
if fileExists("doc/overview.html"):
|
||||||
|
createShortcut(getCurrentDir() / "doc" / "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):
|
||||||
|
try:
|
||||||
|
let arch = execProcess(gccExe, ["-dumpmachine"], nil, {poStdErrToStdOut,
|
||||||
|
poUsePath})
|
||||||
|
when hostCPU == "i386":
|
||||||
|
result = arch.startsWith("i686-")
|
||||||
|
elif hostCPU == "amd64":
|
||||||
|
result = arch.startsWith("x86_64-")
|
||||||
|
else:
|
||||||
|
{.error: "Unknown CPU for Windows.".}
|
||||||
|
except OSError, IOError:
|
||||||
|
result = false
|
||||||
|
|
||||||
|
proc tryDirs(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
|
||||||
|
elif dirExists(d & bits):
|
||||||
|
let x = expandFilename((d & bits) / "bin")
|
||||||
|
if checkGccArch(x): return x
|
||||||
|
|
||||||
|
proc finish() =
|
||||||
|
when defined(windows):
|
||||||
|
let desiredPath = expandFilename(getCurrentDir() / "bin")
|
||||||
|
let p = getUnicodeValue(r"Environment", "Path",
|
||||||
|
HKEY_CURRENT_USER)
|
||||||
|
var alreadyInPath = false
|
||||||
|
var mingWchoices: seq[string] = @[]
|
||||||
|
for x in p.split(';'):
|
||||||
|
let y = expandFilename(if x[0] == '"' and x[^1] == '"':
|
||||||
|
substr(x, 1, x.len-2) else: x)
|
||||||
|
if y == desiredPath: alreadyInPath = true
|
||||||
|
if y.toLowerAscii.contains("mingw"):
|
||||||
|
if dirExists(y) and checkGccArch(y):
|
||||||
|
mingWchoices.add y
|
||||||
|
|
||||||
|
if alreadyInPath:
|
||||||
|
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(desiredPath)
|
||||||
|
if mingWchoices.len == 0:
|
||||||
|
# No mingw in path, so try a few locations:
|
||||||
|
let alternative = tryDirs("dist/mingw", "../mingw", r"C:\mingw")
|
||||||
|
if alternative.len == 0:
|
||||||
|
echo "No MingW found in PATH and no candidate found " &
|
||||||
|
" in the standard locations [Error]"
|
||||||
|
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...")
|
||||||
|
|
||||||
# -------------- tests --------------------------------------------------------
|
# -------------- tests --------------------------------------------------------
|
||||||
|
|
||||||
template `|`(a, b): string = (if a.len > 0: a else: b)
|
template `|`(a, b): string = (if a.len > 0: a else: b)
|
||||||
|
|
@ -440,6 +527,7 @@ of cmdLongOption, cmdShortOption: showHelp()
|
||||||
of cmdArgument:
|
of cmdArgument:
|
||||||
case normalize(op.key)
|
case normalize(op.key)
|
||||||
of "boot": boot(op.cmdLineRest)
|
of "boot": boot(op.cmdLineRest)
|
||||||
|
of "finish": finish()
|
||||||
of "clean": clean(op.cmdLineRest)
|
of "clean": clean(op.cmdLineRest)
|
||||||
of "web": web(op.cmdLineRest)
|
of "web": web(op.cmdLineRest)
|
||||||
of "json2": web("--json2 " & op.cmdLineRest)
|
of "json2": web("--json2 " & op.cmdLineRest)
|
||||||
|
|
@ -453,14 +541,9 @@ of cmdArgument:
|
||||||
of "xz": xz(op.cmdLineRest)
|
of "xz": xz(op.cmdLineRest)
|
||||||
of "nsis": nsis(op.cmdLineRest)
|
of "nsis": nsis(op.cmdLineRest)
|
||||||
of "geninstall": geninstall(op.cmdLineRest)
|
of "geninstall": geninstall(op.cmdLineRest)
|
||||||
of "install": install(op.cmdLineRest)
|
of "distrohelper": geninstall()
|
||||||
of "testinstall": testUnixInstall()
|
of "testinstall": testUnixInstall()
|
||||||
of "test", "tests": tests(op.cmdLineRest)
|
of "test", "tests": tests(op.cmdLineRest)
|
||||||
of "update":
|
|
||||||
when defined(withUpdate):
|
|
||||||
update(op.cmdLineRest)
|
|
||||||
else:
|
|
||||||
quit "this Koch has not been compiled with -d:withUpdate"
|
|
||||||
of "temp": temp(op.cmdLineRest)
|
of "temp": temp(op.cmdLineRest)
|
||||||
of "winrelease": winRelease()
|
of "winrelease": winRelease()
|
||||||
else: showHelp()
|
else: showHelp()
|
||||||
|
|
|
||||||
66
lib/windows/registry.nim
Normal file
66
lib/windows/registry.nim
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
import winlean, os
|
||||||
|
|
||||||
|
type
|
||||||
|
HKEY* = uint
|
||||||
|
|
||||||
|
const
|
||||||
|
HKEY_LOCAL_MACHINE* = HKEY(0x80000002u)
|
||||||
|
HKEY_CURRENT_USER* = HKEY(2147483649)
|
||||||
|
|
||||||
|
RRF_RT_ANY = 0x0000ffff
|
||||||
|
KEY_WOW64_64KEY = 0x0100
|
||||||
|
KEY_WOW64_32KEY = 0x0200
|
||||||
|
KEY_READ = 0x00020019
|
||||||
|
REG_SZ = 1
|
||||||
|
|
||||||
|
proc regOpenKeyEx(hKey: HKEY, lpSubKey: WideCString, ulOptions: int32,
|
||||||
|
samDesired: int32,
|
||||||
|
phkResult: var HKEY): int32 {.
|
||||||
|
importc: "RegOpenKeyExW", dynlib: "Advapi32.dll", stdcall.}
|
||||||
|
|
||||||
|
proc regCloseKey(hkey: HKEY): int32 {.
|
||||||
|
importc: "RegCloseKey", dynlib: "Advapi32.dll", stdcall.}
|
||||||
|
|
||||||
|
proc regGetValue(key: HKEY, lpSubKey, lpValue: WideCString;
|
||||||
|
dwFlags: int32 = RRF_RT_ANY, pdwType: ptr int32,
|
||||||
|
pvData: pointer,
|
||||||
|
pcbData: ptr int32): int32 {.
|
||||||
|
importc: "RegGetValueW", dynlib: "Advapi32.dll", stdcall.}
|
||||||
|
|
||||||
|
template call(f) =
|
||||||
|
let err = f
|
||||||
|
if err != 0:
|
||||||
|
raiseOSError(err.OSErrorCode, astToStr(f))
|
||||||
|
|
||||||
|
proc getUnicodeValue*(path, key: string; handle: HKEY): string =
|
||||||
|
let hh = newWideCString path
|
||||||
|
let kk = newWideCString key
|
||||||
|
var bufsize: int32
|
||||||
|
# try a couple of different flag settings:
|
||||||
|
var flags: int32 = RRF_RT_ANY
|
||||||
|
let err = regGetValue(handle, hh, kk, flags, nil, nil, addr bufsize)
|
||||||
|
if err != 0:
|
||||||
|
var newHandle: HKEY
|
||||||
|
call regOpenKeyEx(handle, hh, 0, KEY_READ or KEY_WOW64_64KEY, newHandle)
|
||||||
|
call regGetValue(newHandle, nil, kk, flags, nil, nil, addr bufsize)
|
||||||
|
var res = newWideCString("", bufsize)
|
||||||
|
call regGetValue(newHandle, nil, kk, flags, nil, cast[pointer](res),
|
||||||
|
addr bufsize)
|
||||||
|
result = res $ bufsize
|
||||||
|
call regCloseKey(newHandle)
|
||||||
|
else:
|
||||||
|
var res = newWideCString("", bufsize)
|
||||||
|
call regGetValue(handle, hh, kk, flags, nil, cast[pointer](res),
|
||||||
|
addr bufsize)
|
||||||
|
result = res $ bufsize
|
||||||
|
|
||||||
|
proc regSetValue(key: HKEY, lpSubKey, lpValueName: WideCString,
|
||||||
|
dwType: int32; lpData: WideCString; cbData: int32): int32 {.
|
||||||
|
importc: "RegSetKeyValueW", dynlib: "Advapi32.dll", stdcall.}
|
||||||
|
|
||||||
|
proc setUnicodeValue*(path, key, val: string; handle: HKey) =
|
||||||
|
let hh = newWideCString path
|
||||||
|
let kk = newWideCString key
|
||||||
|
let vv = newWideCString val
|
||||||
|
call regSetValue(handle, hh, kk, REG_SZ, vv, (vv.len.int32+1)*2)
|
||||||
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue