summary refs log tree commit diff stats
path: root/tools/niminst
diff options
context:
space:
mode:
Diffstat (limited to 'tools/niminst')
-rw-r--r--tools/niminst/EnvVarUpdate.nsh346
-rw-r--r--tools/niminst/buildbat.nimf79
-rw-r--r--tools/niminst/buildsh.nimf287
-rw-r--r--tools/niminst/debcreation.nim239
-rw-r--r--tools/niminst/deinstall.nimf81
-rw-r--r--tools/niminst/inno.nimf182
-rw-r--r--tools/niminst/install.nimf138
-rw-r--r--tools/niminst/makefile.nimf219
-rw-r--r--tools/niminst/nim-file.icobin0 -> 3311 bytes
-rw-r--r--tools/niminst/niminst.nim751
-rw-r--r--tools/niminst/nsis.nimf445
-rw-r--r--tools/niminst/setup.icobin0 -> 2551 bytes
-rw-r--r--tools/niminst/uninstall.icobin0 -> 2226 bytes
13 files changed, 2767 insertions, 0 deletions
diff --git a/tools/niminst/EnvVarUpdate.nsh b/tools/niminst/EnvVarUpdate.nsh
new file mode 100644
index 000000000..4340b8f3c
--- /dev/null
+++ b/tools/niminst/EnvVarUpdate.nsh
@@ -0,0 +1,346 @@
+/**
+ *  EnvVarUpdate.nsh
+ *    : Environmental Variables: append, prepend, and remove entries
+ *
+ *     WARNING: If you use StrFunc.nsh header then include it before this file
+ *              with all required definitions. This is to avoid conflicts
+ *
+ *  Usage:
+ *    ${EnvVarUpdate} "ResultVar" "EnvVarName" "Action" "RegLoc" "PathString"
+ *
+ *  Credits:
+ *  Version 1.0 
+ *  * Cal Turney (turnec2)
+ *  * Amir Szekely (KiCHiK) and e-circ for developing the forerunners of this
+ *    function: AddToPath, un.RemoveFromPath, AddToEnvVar, un.RemoveFromEnvVar,
+ *    WriteEnvStr, and un.DeleteEnvStr
+ *  * Diego Pedroso (deguix) for StrTok
+ *  * Kevin English (kenglish_hi) for StrContains
+ *  * Hendri Adriaens (Smile2Me), Diego Pedroso (deguix), and Dan Fuhry  
+ *    (dandaman32) for StrReplace
+ *
+ *  Version 1.1 (compatibility with StrFunc.nsh)
+ *  * techtonik
+ *
+ *  http://nsis.sourceforge.net/Environmental_Variables:_append%2C_prepend%2C_and_remove_entries
+ *
+ */
+
+
+!ifndef ENVVARUPDATE_FUNCTION
+!define ENVVARUPDATE_FUNCTION
+!verbose push
+!verbose 3
+!include "LogicLib.nsh"
+!include "WinMessages.NSH"
+!include "StrFunc.nsh"
+
+; ---- Fix for conflict if StrFunc.nsh is already includes in main file -----------------------
+!macro _IncludeStrFunction StrFuncName
+  !ifndef ${StrFuncName}_INCLUDED
+    ${${StrFuncName}}
+  !endif
+  !ifndef Un${StrFuncName}_INCLUDED
+    ${Un${StrFuncName}}
+  !endif
+  !define un.${StrFuncName} "${Un${StrFuncName}}"
+!macroend
+
+!insertmacro _IncludeStrFunction StrTok
+!insertmacro _IncludeStrFunction StrStr
+!insertmacro _IncludeStrFunction StrRep
+
+; ---------------------------------- Macro Definitions ----------------------------------------
+!macro _EnvVarUpdateConstructor ResultVar EnvVarName Action Regloc PathString
+  Push "${EnvVarName}"
+  Push "${Action}"
+  Push "${RegLoc}"
+  Push "${PathString}"
+    Call EnvVarUpdate
+  Pop "${ResultVar}"
+!macroend
+!define EnvVarUpdate '!insertmacro "_EnvVarUpdateConstructor"'
+ 
+!macro _unEnvVarUpdateConstructor ResultVar EnvVarName Action Regloc PathString
+  Push "${EnvVarName}"
+  Push "${Action}"
+  Push "${RegLoc}"
+  Push "${PathString}"
+    Call un.EnvVarUpdate
+  Pop "${ResultVar}"
+!macroend
+!define un.EnvVarUpdate '!insertmacro "_unEnvVarUpdateConstructor"'
+; ---------------------------------- Macro Definitions end-------------------------------------
+ 
+;----------------------------------- EnvVarUpdate start----------------------------------------
+!define hklm_all_users     'HKLM "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"'
+!define hkcu_current_user  'HKCU "Environment"'
+ 
+!macro EnvVarUpdate UN
+ 
+Function ${UN}EnvVarUpdate
+ 
+  Push $0
+  Exch 4
+  Exch $1
+  Exch 3
+  Exch $2
+  Exch 2
+  Exch $3
+  Exch
+  Exch $4
+  Push $5
+  Push $6
+  Push $7
+  Push $8
+  Push $9
+  Push $R0
+ 
+  /* After this point:
+  -------------------------
+     $0 = ResultVar     (returned)
+     $1 = EnvVarName    (input)
+     $2 = Action        (input)
+     $3 = RegLoc        (input)
+     $4 = PathString    (input)
+     $5 = Orig EnvVar   (read from registry)
+     $6 = Len of $0     (temp)
+     $7 = tempstr1      (temp)
+     $8 = Entry counter (temp)
+     $9 = tempstr2      (temp)
+     $R0 = tempChar     (temp)  */
+ 
+  ; Step 1:  Read contents of EnvVarName from RegLoc
+  ;
+  ; Check for empty EnvVarName
+  ${If} $1 == ""
+    SetErrors
+    DetailPrint "ERROR: EnvVarName is blank"
+    Goto EnvVarUpdate_Restore_Vars
+  ${EndIf}
+ 
+  ; Check for valid Action
+  ${If}    $2 != "A"
+  ${AndIf} $2 != "P"
+  ${AndIf} $2 != "R"
+    SetErrors
+    DetailPrint "ERROR: Invalid Action - must be A, P, or R"
+    Goto EnvVarUpdate_Restore_Vars
+  ${EndIf}
+ 
+  ${If} $3 == HKLM
+    ReadRegStr $5 ${hklm_all_users} $1     ; Get EnvVarName from all users into $5
+  ${ElseIf} $3 == HKCU
+    ReadRegStr $5 ${hkcu_current_user} $1  ; Read EnvVarName from current user into $5
+  ${Else}
+    SetErrors
+    DetailPrint 'ERROR: Action is [$3] but must be "HKLM" or HKCU"'
+    Goto EnvVarUpdate_Restore_Vars
+  ${EndIf}
+ 
+  ; Check for empty PathString
+  ${If} $4 == ""
+    SetErrors
+    DetailPrint "ERROR: PathString is blank"
+    Goto EnvVarUpdate_Restore_Vars
+  ${EndIf}
+ 
+  Push $6
+  Push $7
+  Push $8
+  StrLen $7 $4  
+  StrLen $6 $5
+  IntOp $8 $6 + $7
+  ${If} $5 == ""
+  ${OrIf} $8 >= ${NSIS_MAX_STRLEN}
+    SetErrors
+    DetailPrint "Current $1 length ($6) too long to modify in NSIS; set manually if needed"
+    Pop $8
+    Pop $7
+    Pop $6
+    Goto EnvVarUpdate_Restore_Vars
+  ${EndIf}
+  Pop $8
+  Pop $7
+  Pop $6
+
+  ; Make sure we've got some work to do
+  ${If} $5 == ""
+  ${AndIf} $2 == "R"
+    SetErrors
+    DetailPrint "$1 is empty - Nothing to remove"
+    Goto EnvVarUpdate_Restore_Vars
+  ${EndIf}
+ 
+  ; Step 2: Scrub EnvVar
+  ;
+  StrCpy $0 $5                             ; Copy the contents to $0
+  ; Remove spaces around semicolons (NOTE: spaces before the 1st entry or
+  ; after the last one are not removed here but instead in Step 3)
+  ${If} $0 != ""                           ; If EnvVar is not empty ...
+    ${Do}
+      ${${UN}StrStr} $7 $0 " ;"
+      ${If} $7 == ""
+        ${ExitDo}
+      ${EndIf}
+      ${${UN}StrRep} $0  $0 " ;" ";"         ; Remove '<space>;'
+    ${Loop}
+    ${Do}
+      ${${UN}StrStr} $7 $0 "; "
+      ${If} $7 == ""
+        ${ExitDo}
+      ${EndIf}
+      ${${UN}StrRep} $0  $0 "; " ";"         ; Remove ';<space>'
+    ${Loop}
+    ${Do}
+      ${${UN}StrStr} $7 $0 ";;" 
+      ${If} $7 == ""
+        ${ExitDo}
+      ${EndIf}
+      ${${UN}StrRep} $0  $0 ";;" ";"
+    ${Loop}
+ 
+    ; Remove a leading or trailing semicolon from EnvVar
+    StrCpy  $7  $0 1 0
+    ${If} $7 == ";"
+      StrCpy $0  $0 "" 1                   ; Change ';<EnvVar>' to '<EnvVar>'
+    ${EndIf}
+    StrLen $6 $0
+    IntOp $6 $6 - 1
+    StrCpy $7  $0 1 $6
+    ${If} $7 == ";"
+     StrCpy $0  $0 $6                      ; Change ';<EnvVar>' to '<EnvVar>'
+    ${EndIf}
+    ; DetailPrint "Scrubbed $1: [$0]"      ; Uncomment to debug
+  ${EndIf}
+ 
+  /* Step 3. Remove all instances of the target path/string (even if "A" or "P")
+     $6 = bool flag (1 = found and removed PathString)
+     $7 = a string (e.g. path) delimited by semicolon(s)
+     $8 = entry counter starting at 0
+     $9 = copy of $0
+     $R0 = tempChar      */
+ 
+  ${If} $5 != ""                           ; If EnvVar is not empty ...
+    StrCpy $9 $0
+    StrCpy $0 ""
+    StrCpy $8 0
+    StrCpy $6 0
+ 
+    ${Do}
+      ${${UN}StrTok} $7 $9 ";" $8 "0"      ; $7 = next entry, $8 = entry counter
+ 
+      ${If} $7 == ""                       ; If we've run out of entries,
+        ${ExitDo}                          ;    were done
+      ${EndIf}                             ;
+ 
+      ; Remove leading and trailing spaces from this entry (critical step for Action=Remove)
+      ${Do}
+        StrCpy $R0  $7 1
+        ${If} $R0 != " "
+          ${ExitDo}
+        ${EndIf}
+        StrCpy $7   $7 "" 1                ;  Remove leading space
+      ${Loop}
+      ${Do}
+        StrCpy $R0  $7 1 -1
+        ${If} $R0 != " "
+          ${ExitDo}
+        ${EndIf}
+        StrCpy $7   $7 -1                  ;  Remove trailing space
+      ${Loop}
+      ${If} $7 == $4                       ; If string matches, remove it by not appending it
+        StrCpy $6 1                        ; Set 'found' flag
+      ${ElseIf} $7 != $4                   ; If string does NOT match
+      ${AndIf}  $0 == ""                   ;    and the 1st string being added to $0,
+        StrCpy $0 $7                       ;    copy it to $0 without a prepended semicolon
+      ${ElseIf} $7 != $4                   ; If string does NOT match
+      ${AndIf}  $0 != ""                   ;    and this is NOT the 1st string to be added to $0,
+        StrCpy $0 $0;$7                    ;    append path to $0 with a prepended semicolon
+      ${EndIf}                             ;
+ 
+      IntOp $8 $8 + 1                      ; Bump counter
+    ${Loop}                                ; Check for duplicates until we run out of paths
+  ${EndIf}
+ 
+  ; Step 4:  Perform the requested Action
+  ;
+  ${If} $2 != "R"                          ; If Append or Prepend
+    ${If} $6 == 1                          ; And if we found the target
+      DetailPrint "Target is already present in $1. It will be removed and"
+    ${EndIf}
+    ${If} $0 == ""                         ; If EnvVar is (now) empty
+      StrCpy $0 $4                         ;   just copy PathString to EnvVar
+      ${If} $6 == 0                        ; If found flag is either 0
+      ${OrIf} $6 == ""                     ; or blank (if EnvVarName is empty)
+        DetailPrint "$1 was empty and has been updated with the target"
+      ${EndIf}
+    ${ElseIf} $2 == "A"                    ;  If Append (and EnvVar is not empty),
+      StrCpy $0 $0;$4                      ;     append PathString
+      ${If} $6 == 1
+        DetailPrint "appended to $1"
+      ${Else}
+        DetailPrint "Target was appended to $1"
+      ${EndIf}
+    ${Else}                                ;  If Prepend (and EnvVar is not empty),
+      StrCpy $0 $4;$0                      ;     prepend PathString
+      ${If} $6 == 1
+        DetailPrint "prepended to $1"
+      ${Else}
+        DetailPrint "Target was prepended to $1"
+      ${EndIf}
+    ${EndIf}
+  ${Else}                                  ; If Action = Remove
+    ${If} $6 == 1                          ;   and we found the target
+      DetailPrint "Target was found and removed from $1"
+    ${Else}
+      DetailPrint "Target was NOT found in $1 (nothing to remove)"
+    ${EndIf}
+    ${If} $0 == ""
+      DetailPrint "$1 is now empty"
+    ${EndIf}
+  ${EndIf}
+ 
+  ; Step 5:  Update the registry at RegLoc with the updated EnvVar and announce the change
+  ;
+  ClearErrors
+  ${If} $3  == HKLM
+    WriteRegExpandStr ${hklm_all_users} $1 $0     ; Write it in all users section
+  ${ElseIf} $3 == HKCU
+    WriteRegExpandStr ${hkcu_current_user} $1 $0  ; Write it to current user section
+  ${EndIf}
+ 
+  IfErrors 0 +4
+    MessageBox MB_OK|MB_ICONEXCLAMATION "Could not write updated $1 to $3"
+    DetailPrint "Could not write updated $1 to $3"
+    Goto EnvVarUpdate_Restore_Vars
+ 
+  ; "Export" our change
+  SendMessage ${HWND_BROADCAST} ${WM_WININICHANGE} 0 "STR:Environment" /TIMEOUT=5000
+ 
+  EnvVarUpdate_Restore_Vars:
+  ;
+  ; Restore the user's variables and return ResultVar
+  Pop $R0
+  Pop $9
+  Pop $8
+  Pop $7
+  Pop $6
+  Pop $5
+  Pop $4
+  Pop $3
+  Pop $2
+  Pop $1
+  Push $0  ; Push my $0 (ResultVar)
+  Exch
+  Pop $0   ; Restore his $0
+ 
+FunctionEnd
+ 
+!macroend   ; EnvVarUpdate UN
+!insertmacro EnvVarUpdate ""
+!insertmacro EnvVarUpdate "un."
+;----------------------------------- EnvVarUpdate end----------------------------------------
+ 
+!verbose pop
+!endif
diff --git a/tools/niminst/buildbat.nimf b/tools/niminst/buildbat.nimf
new file mode 100644
index 000000000..6767461e3
--- /dev/null
+++ b/tools/niminst/buildbat.nimf
@@ -0,0 +1,79 @@
+#? stdtmpl(subsChar='?') | standard
+#proc generateBuildBatchScript(c: ConfigData, winIndex, cpuIndex32, cpuIndex64: int): string =
+#  result = "@echo off\nREM Generated by niminst\n"
+SET CC=gcc
+SET LINKER=gcc
+SET COMP_FLAGS=?{c.ccompiler.flags}
+SET LINK_FLAGS=?{c.linker.flags}
+SET BIN_DIR=?{firstBinPath(c).toWin}
+
+REM Detect gcc arch
+IF DEFINED ARCH (
+  ECHO Forcing %CC% arch
+) ELSE (
+  ECHO Detecting %CC% arch
+  ECHO int main^(^) { return sizeof^(void *^); } | gcc -xc - -o archtest && archtest
+  IF ERRORLEVEL 4 SET ARCH=32
+  IF ERRORLEVEL 8 SET ARCH=64
+  del archtest.*
+)
+ECHO Building with %ARCH% bit %CC%
+
+if EXIST ..\koch.nim SET BIN_DIR=..\bin
+
+if NOT EXIST %BIN_DIR%\nul mkdir %BIN_DIR%
+
+REM call the compiler:
+
+IF %ARCH% EQU 32 (
+
+#  block win32:
+#    var linkCmd = ""
+#    if cpuIndex32 != -1:
+#      for ff in items(c.cfiles[winIndex][cpuIndex32]):
+#        let f = ff.toWin
+  ECHO %CC% %COMP_FLAGS% -Ic_code -c ?{f} -o ?{changeFileExt(f, "o")}
+  CALL %CC% %COMP_FLAGS% -Ic_code -c ?{f} -o ?{changeFileExt(f, "o")}
+#        linkCmd.add(" " & changeFileExt(f, "o"))
+  IF ERRORLEVEL 1 (GOTO:END)
+#      end for
+#    end if
+
+  ECHO %LINKER% -o ?{"%BIN_DIR%"\toLowerAscii(c.name)}.exe ?linkCmd %LINK_FLAGS%
+  CALL %LINKER% -o ?{"%BIN_DIR%"\toLowerAscii(c.name)}.exe ?linkCmd %LINK_FLAGS%
+
+#  end block
+
+) ELSE IF %ARCH% EQU 64 (
+
+#  block win64:
+#    var linkCmd = ""
+#    if cpuIndex64 != -1:
+#      for ff in items(c.cfiles[winIndex][cpuIndex64]):
+#        let f = ff.toWin
+  ECHO %CC% %COMP_FLAGS% -Ic_code -c ?{f} -o ?{changeFileExt(f, "o")}
+  CALL %CC% %COMP_FLAGS% -Ic_code -c ?{f} -o ?{changeFileExt(f, "o")}
+#        linkCmd.add(" " & changeFileExt(f, "o"))
+  IF ERRORLEVEL 1 (GOTO:END)
+#      end for
+#    end if
+
+  ECHO %LINKER% -o ?{"%BIN_DIR%"\toLowerAscii(c.name)}.exe ?linkCmd %LINK_FLAGS%
+  CALL %LINKER% -o ?{"%BIN_DIR%"\toLowerAscii(c.name)}.exe ?linkCmd %LINK_FLAGS%
+
+#  end block
+)
+
+:END
+IF ERRORLEVEL 1 (
+    ECHO FAILURE
+    ECHO.
+    ECHO CSource compilation failed. Please check that the gcc compiler is in
+    ECHO the PATH environment variable, and that you are calling the batch script
+    ECHO that matches the target architecture of the compiler.
+    ECHO.
+    ECHO Use build.bat to autodetect the compiler architecture.
+) ELSE (
+    ECHO SUCCESS
+)
+exit /b %ERRORLEVEL%
diff --git a/tools/niminst/buildsh.nimf b/tools/niminst/buildsh.nimf
new file mode 100644
index 000000000..063a02779
--- /dev/null
+++ b/tools/niminst/buildsh.nimf
@@ -0,0 +1,287 @@
+#? stdtmpl(subsChar='?') | standard
+#proc generateBuildShellScript(c: ConfigData): string =
+#  result = "#!/bin/sh\n# Generated from niminst\n" &
+#           "# Template is in tools/niminst/buildsh.nimf\n" &
+#           "# To regenerate run ``niminst csource`` or ``koch csource``\n"
+
+set -e
+
+while :
+do
+  case "$1" in
+    --os)
+      optos=$2
+      shift 2
+      ;;
+    --cpu)
+      optcpu=$2
+      shift 2
+      ;;
+    --osname)
+      optosname=$2
+      shift 2
+      ;;
+    --parallel)
+      parallel=$2
+      shift 2
+      ;;
+    --extraBuildArgs)
+      extraBuildArgs=" $2"
+      shift 2
+      ;;
+    -h | --help)
+      echo "Options:"
+      echo "  --os <OS>"
+      echo "  --cpu <CPU architecture>"
+      echo "  --osname <name>           Additional OS specification (used for Android)"
+      echo "  --extraBuildArgs <args>   Additional arguments passed to the compiler"
+      echo "  --parallel <number>       Multiprocess build. Requires GNU parallel"
+      exit 0
+      ;;
+    --) # End of all options
+      shift
+      break;
+      ;;
+    -*)
+      echo 2>&1 "Error: Unknown option: $1" >&2
+      exit 1
+      ;;
+    *)  # No more options
+      break
+      ;;
+  esac
+done
+
+parallel="${parallel:-0}"
+CC="${CC:-gcc}"
+if [ "$parallel" -gt 1 ]; then
+  if ! command -v sem > /dev/null; then
+    echo "Error: GNU parallel is required to use --parallel"
+    exit 1
+  fi
+  CC="sem -j $parallel --id $$ ${CC}"
+fi
+COMP_FLAGS="${CPPFLAGS:-} ${CFLAGS:-} ?{c.ccompiler.flags}$extraBuildArgs"
+LINK_FLAGS="${LDFLAGS:-} ?{c.linker.flags}"
+PS4=""
+#  add(result, "# platform detection\n")
+ucpu=`uname -m`
+uos=`uname`
+uosname=
+#  add(result, "# bin dir detection\n")
+binDir=?{firstBinPath(c).toUnix}
+
+if [ -s ../koch.nim ]; then
+  binDir="../bin"
+fi
+
+if [ ! -d $binDir ]; then
+  mkdir $binDir
+fi
+
+#  add(result, "# override OS, CPU and OS Name with command-line arguments\n")
+if [ -n "$optos" ]; then
+  uos="$optos"
+fi
+if [ -n "$optcpu" ]; then
+  ucpu="$optcpu"
+fi
+if [ -n "$optcpu" ]; then
+  uosname="$optosname"
+fi
+
+#  add(result, "# convert to lower case:\n")
+ucpu=`echo $ucpu | tr "[:upper:]" "[:lower:]"`
+uos=`echo $uos | tr "[:upper:]" "[:lower:]"`
+uosname=`echo $uosname | tr "[:upper:]" "[:lower:]"`
+
+case $uos in
+  *linux* )
+    myos="linux"
+    LINK_FLAGS="$LINK_FLAGS -ldl -lm -lrt"
+    ;;
+  *dragonfly* )
+    myos="dragonfly"
+    LINK_FLAGS="$LINK_FLAGS -lm"
+    ;;
+  *freebsd* )
+    myos="freebsd"
+    CC="clang"
+    LINK_FLAGS="$LINK_FLAGS -lm"
+    ;;
+  *crossos* )
+    myos="crossos"
+    CC="clang"
+    LINK_FLAGS="$LINK_FLAGS -lm"
+    ;;
+  *openbsd* )
+    myos="openbsd"
+    CC="clang"
+    LINK_FLAGS="$LINK_FLAGS -lm"
+    ;;
+  *netbsd* )
+    myos="netbsd"
+    LINK_FLAGS="$LINK_FLAGS -lm"
+    ucpu=`uname -p`
+    ;;
+  *darwin* )
+    myos="macosx"
+    CC="clang"
+    LINK_FLAGS="$LINK_FLAGS -ldl -lm"
+    if [ "$HOSTTYPE" = "x86_64" ] ; then
+      ucpu="amd64"
+    fi
+    ;;
+  *aix* )
+    myos="aix"
+    LINK_FLAGS="$LINK_FLAGS -ldl -lm"
+    ;;
+  *solaris* | *sun* )
+    myos="solaris"
+    LINK_FLAGS="$LINK_FLAGS -ldl -lm -lsocket -lnsl"
+    ;;
+  *SunOS* )
+    myos="solaris"
+    LINK_FLAGS="$LINK_FLAGS -ldl -lm -lsocket -lnsl"
+    isOpenIndiana="yes"
+    ;;
+  *haiku* )
+    myos="haiku"
+    LINK_FLAGS="$LINK_FLAGS -lroot -lnetwork"
+    ;;
+  *mingw* | *msys* )
+    myos="windows"
+    ;;
+  *android* )
+    myos="android"
+    LINK_FLAGS="$LINK_FLAGS -ldl -lm -lrt"
+    LINK_FLAGS="$LINK_FLAGS -landroid-glob"
+    ;;
+  *)
+    echo 2>&1 "Error: unknown operating system: $uos"
+    exit 1
+    ;;
+esac
+
+case $ucpu in
+  *i386* | *i486* | *i586* | *i686* | *bepc* | *i86pc* )
+    if [ "$isOpenIndiana" = "yes" ] || [ `uname -o` == "illumos" ] ; then
+      mycpu="amd64"
+    else
+      mycpu="i386"
+    fi
+    ;;
+  *amd*64* | *x86-64* | *x86_64* )
+    mycpu="amd64" ;;
+  *sparc*|*sun* )
+    mycpu="sparc"
+    if [ "$myos" = "linux" ] ; then
+      if [ "$(getconf LONG_BIT)" = "64" ]; then
+        mycpu="sparc64"
+      elif [ "$(isainfo -b)" = "64" ]; then
+        mycpu="sparc64"
+      fi
+    fi
+    ;;
+  *ppc64le* )
+    mycpu="powerpc64el" ;;
+  *ppc64* )
+    if [ "$myos" = "linux" ] ; then
+      COMP_FLAGS="$COMP_FLAGS -m64"
+      LINK_FLAGS="$LINK_FLAGS -m64"
+    fi
+    mycpu="powerpc64" ;;
+  *power*|*ppc* )
+    if [ "$myos" = "freebsd" ] ; then
+      if [ "$ucpu" != "powerpc" ] ; then
+        COMP_FLAGS="$COMP_FLAGS -m64"
+        LINK_FLAGS="$LINK_FLAGS -m64"
+      fi
+      mycpu=`uname -p`
+      case $mycpu in
+        powerpc64le)
+        mycpu="powerpc64el"
+      esac
+    else
+      mycpu="powerpc"
+    fi
+    ;;
+  *hppa*)
+    mycpu="hppa" ;;
+  *ia64*)
+    mycpu="ia64" ;;
+  *m68k*)
+    mycpu="m68k" ;;
+  *mips* )
+    mycpu="$("$CC" -dumpmachine | sed 's/-.*//')"
+    case $mycpu in
+      mips|mipsel|mips64|mips64el)
+        ;;
+      *)
+        echo 2>&1 "Error: unknown MIPS target: $mycpu"
+        exit 1
+    esac
+    ;;
+  *alpha* )
+    mycpu="alpha" ;;
+  *aarch64*|*arm64* )
+    mycpu="arm64" ;;
+  *arm*|*armv6l*|*armv7l*|*armv8l* )
+    mycpu="arm" ;;
+  *riscv64|riscv* )
+    mycpu="riscv64" ;;
+  *e2k* )
+    mycpu="e2k" ;;
+  *loongarch64* )
+    mycpu="loongarch64" ;;
+  *)
+    echo 2>&1 "Error: unknown processor: $ucpu"
+    exit 1
+    ;;
+esac
+
+case $uosname in
+  *android* )
+    LINK_FLAGS="$LINK_FLAGS -landroid-glob"
+    myosname="android"
+    myos="android"
+    ;;
+esac
+
+#  add(result, "# call the compiler:\n")
+echo \# OS:  $myos
+echo \# CPU: $mycpu
+
+case $myos in
+#  for osA in 1..c.oses.len:
+?{c.oses[osA-1]})
+  case $mycpu in
+#    for cpuA in 1..c.cpus.len:
+  ?{c.cpus[cpuA-1]})
+    set -x
+#      var linkCmd = ""
+#      for ff in items(c.cfiles[osA][cpuA]):
+#        let f = ff.toUnix
+    $CC $COMP_FLAGS -Ic_code -c ?{f} -o ?{changeFileExt(f, "o")}
+#        add(linkCmd, " \\\n" & changeFileExt(f, "o"))
+#      end for
+    if [ "$parallel" -gt 0 ]; then
+      sem --wait --id $$
+    fi
+    $CC -o ?{"$binDir/" & toLowerAscii(c.name)} ?linkCmd $LINK_FLAGS
+    ;;
+#    end for
+  *)
+    echo 2>&1 "Error: no C code generated for: [$myos: $mycpu]"
+    exit 1
+    ;;
+  esac
+  ;;
+#  end for
+*)
+  echo 2>&1 "Error: no C code generated for: [$myos: $mycpu]"
+  exit 1
+  ;;
+esac
+
+: SUCCESS
diff --git a/tools/niminst/debcreation.nim b/tools/niminst/debcreation.nim
new file mode 100644
index 000000000..219cb44ce
--- /dev/null
+++ b/tools/niminst/debcreation.nim
@@ -0,0 +1,239 @@
+#
+#
+#        The Nim Installation Generator
+#        (c) Copyright 2012 Dominik Picheta
+#
+#    See the file "copying.txt", included in this
+#    distribution, for details about the copyright.
+#
+
+import osproc, times, os, strutils
+
+
+when defined(nimPreviewSlimSystem):
+  import std/[assertions, syncio]
+
+# http://www.debian.org/doc/manuals/maint-guide/
+
+# Required files for debhelper.
+# -- control
+# -- copyright
+# -- changelog
+# -- rules
+
+type
+  TDebOptions* = object
+    buildDepends*, pkgDepends*, shortDesc*: string
+    licenses*: seq[tuple[files, license: string]]
+
+template addN(r: string) =
+  result.add(r)
+  result.add("\n")
+
+proc createControl(pkgName, maintainer, shortDesc, desc: string,
+                   buildDepends, pkgDepends: string = ""): string =
+  ## pkgName: Should be the package name, no spaces.
+  ## maintainer: firstName lastName <email>
+  ## shortDesc: short description of the application
+  ## desc: long description of the application
+  ## buildDepends: what the build depends on (compiling from source),
+  ##               this needs to be in the format deb accepts. For example,
+  ##               for gcc: ``gcc (>= 4:4.3.2)``
+  ##               Multiple dependencies should be separated by commas.
+  ## pkgDepends: Same as buildDepends except that this specifies the
+  ##             dependencies that the compiled application depends on.
+
+
+  result = ""
+
+  addN("Source: " & pkgName)
+  addN("Maintainer: " & maintainer)
+  addN("Section: misc")
+  addN("Priority: optional")
+  addN("Standards-Version: 3.9.2")
+  addN("Build-Depends: debhelper (>= 8)" &
+        (if buildDepends != "": ", " & buildDepends else: ""))
+  addN("\n")
+  addN("Package: " & pkgName)
+  addN("Architecture: any")
+  addN("Depends: ${shlibs:Depends}, ${misc:Depends}" &
+        (if pkgDepends != "": ", " & pkgDepends else: ""))
+
+  var formattedDesc = ""
+  for line in splitLines(desc):
+    if line == "":
+      formattedDesc.add(" .\n")
+    else:
+      formattedDesc.add(" " & line & "\n")
+
+  addN("Description: " & shortDesc & "\n" & formattedDesc)
+
+proc createCopyright(pkgName, mtnName, mtnEmail, version: string,
+                     licenses: seq[tuple[files, license: string]]): string =
+  ## pkgName: Package name
+  ## mtnName: Maintainer name
+  ## mtnEmail: Maintainer email
+  ## version: package version
+  ## licenses: files: This specifies the files that the `license` covers,
+  ##           for example, it might be ``lib/*`` to cover the whole ``lib`` dir
+  ##           license: This specifies the license, for example gpl2, or lgpl.
+
+  result = ""
+  addN("Maintainer name: " & mtnName)
+  addN("Email-Address: " & mtnEmail)
+  addN("Date: " & $getTime())
+  addN("Package Name: " & pkgName)
+  addN("Version: " & version)
+  for f, license in items(licenses):
+    addN("Files: " & f)
+    addN("License: " & license)
+
+proc formatDateTime(t: DateTime, timezone: string): string =
+  var day = ($t.weekday)[0..2] & ", "
+
+  return "$1$2 $3 $4 $5:$6:$7 $8" % [day, intToStr(t.monthday, 2),
+    ($t.month)[0..2], $t.year, intToStr(t.hour, 2), intToStr(t.minute, 2),
+    intToStr(t.second, 2), timezone]
+
+proc createChangelog(pkgName, version, maintainer: string): string =
+  ## pkgName: package name
+  ## version: package version
+  ## maintainer: firstName lastName <email>
+  result = ""
+  addN(pkgName & " (" & version & "-1) unstable; urgency=low")
+  addN("")
+  addN("  * Initial release.")
+  addN("")
+  addN(" -- " & maintainer & "  " &
+       formatDateTime(utc(getTime()), "+0000"))
+
+proc createRules(): string =
+  ## Creates a nim application-agnostic rules file for building deb packages.
+  ## Please note: this assumes the c sources have been built and the
+  ## ``build.sh`` and ``install.sh`` files are available.
+  result = ""
+  addN("#!/usr/bin/make -f")
+  addN("%:")
+  addN("\tdh $@\n")
+  addN("dh_install:")
+  addN("\tdh_install --sourcedir=debian/tmp")
+  addN("override_dh_auto_clean:")
+  addN("\tfind . -name *.o -exec rm {} \\;")
+  addN("override_dh_auto_build:")
+  addN("\t./build.sh")
+  addN("override_dh_auto_install:")
+  addN("\t./install.sh debian/tmp")
+
+proc createIncludeBinaries(binaries: seq[string]): string =
+  return join(binaries, "\n")
+
+proc createDotInstall(pkgName: string, binaries, config, docs,
+    lib: seq[string]): string =
+  result = ""
+  for b in binaries:
+    addN(pkgName / b & " " & "usr/bin/")
+  for c in config:
+    addN(pkgName / c & " " & "etc/")
+  for d in docs:
+    addN(pkgName / d & " " & "usr/share/doc/nim/")
+  for l1 in lib:
+    addN(pkgName / l1 & " " & "usr/lib/nim")
+
+proc makeMtn(name, email: string): string =
+  return name & " <" & email & ">"
+
+proc assertSuccess(exitCode: int) =
+  doAssert(exitCode == QuitSuccess)
+
+proc prepDeb*(packName, version, mtnName, mtnEmail, shortDesc, desc: string,
+              licenses: seq[tuple[files, license: string]], binaries,
+              config, docs, lib: seq[string],
+              buildDepends, pkgDepends = "") =
+  ## binaries/config/docs/lib: files relative to nim's root, that need to
+  ##   be installed.
+
+  let pkgName = packName.toLowerAscii()
+
+  var workingDir = getTempDir() / "niminst" / "deb"
+  var upstreamSource = (pkgName & "-" & version)
+
+  echo("Making sure build.sh and install.sh are +x")
+  assertSuccess execCmd("chmod +x \"" &
+    (workingDir / upstreamSource / "build.sh") & "\"")
+  assertSuccess execCmd("chmod +x \"" &
+    (workingDir / upstreamSource / "install.sh") & "\"")
+
+  var tarCmd = "tar pczf \"" &
+      (pkgName & "_" & version & ".orig.tar.gz") &
+      "\" \"" & upstreamSource & "\""
+  echo(tarCmd)
+  assertSuccess execCmd("cd \"" & workingDir & "\" && " & tarCmd)
+
+  echo("Creating necessary files in debian/")
+  createDir(workingDir / upstreamSource / "debian")
+
+  template writeDebian(f, s: string) =
+    writeFile(workingDir / upstreamSource / "debian" / f, s)
+
+  var controlFile = createControl(pkgName, makeMtn(mtnName, mtnEmail),
+      shortDesc, desc, buildDepends, pkgDepends)
+  echo("debian/control")
+  writeDebian("control", controlFile)
+
+  var copyrightFile = createCopyright(pkgName, mtnName, mtnEmail, version,
+      licenses)
+  echo("debian/copyright")
+  writeDebian("copyright", copyrightFile)
+
+  var changelogFile = createChangelog(pkgName, version,
+      makeMtn(mtnName, mtnEmail))
+  echo("debian/changelog")
+  writeDebian("changelog", changelogFile)
+
+  echo("debian/rules")
+  writeDebian("rules", createRules())
+
+  echo("debian/compat")
+  writeDebian("compat", "8")
+
+  echo("debian/" & pkgName & ".install")
+  writeDebian(pkgName & ".install",
+    createDotInstall(pkgName, binaries, config, docs, lib))
+
+  # Other things..
+  createDir(workingDir / upstreamSource / "debian" / "source")
+  echo("debian/source/format")
+  writeDebian("source" / "format",
+            "3.0 (quilt)")
+  echo("debian/source/include-binaries")
+  writeFile(workingDir / upstreamSource / "debian" / "source" / "include-binaries",
+            createIncludeBinaries(binaries))
+
+  echo("All done, you can now build.")
+  echo("Before you do however, make sure the files in " &
+    workingDir / upstreamSource / "debian" & " are correct.")
+  echo("Change your directory to: " & workingDir / upstreamSource)
+  echo("And execute `debuild -us -uc` to build the .deb")
+
+when isMainModule:
+  #var controlFile = createControl("nim", "Dominik Picheta <morfeusz8@gmail.com>",
+  # "The Nim compiler", "Compiler for the Nim programming language", "gcc (>= 4:4.3.2)", "gcc (>= 4:4.3.2)")
+
+  #echo(controlFile)
+
+  #var copyrightFile = createCopyright("nim", "Dominik Picheta", "morfeusz8@a.b", "0.8.14",
+  #    @[("bin/nim", "gpl2"), ("lib/*", "lgpl")])
+
+  #echo copyrightFile
+
+  #var changelogFile = createChangelog("nim", "0.8.14", "Dom P <m@b.c>")
+  #echo(changelogFile)
+
+  #echo(createRules())
+
+  prepDeb("nim", "0.9.2", "Dominik Picheta", "morfeusz8@gmail.com",
+    "The Nim compiler", "Compiler for the Nim programming language",
+    @[("bin/nim", "MIT"), ("lib/*", "MIT")],
+    @["bin/nim"], @["config/*"], @["doc/*"], @["lib/*"],
+    "gcc (>= 4:4.3.2)", "gcc (>= 4:4.3.2)")
+
diff --git a/tools/niminst/deinstall.nimf b/tools/niminst/deinstall.nimf
new file mode 100644
index 000000000..0dcfda75e
--- /dev/null
+++ b/tools/niminst/deinstall.nimf
@@ -0,0 +1,81 @@
+#? stdtmpl(subsChar='?') | standard
+#proc generateDeinstallScript(c: ConfigData): string =
+#  result = "#!/bin/sh\n# Generated by niminst\n"
+#  var proj = c.name.toLowerAscii
+
+if [ $# -eq 1 ] ; then
+  case $1 in
+    "--help"|"-h"|"help"|"h")
+      echo "?c.displayName deinstallation script"
+      echo "Usage: [sudo] sh deinstall.sh DIR"
+      echo "Where DIR may be:"
+      echo "  /usr/bin"
+      echo "  /usr/local/bin"
+      echo "  /opt"
+      echo "  <some other dir> (treated similar '/opt')"
+      exit 1
+      ;;
+    "/usr/bin")
+      bindir=/usr/bin
+      configdir=/etc/?proj
+      libdir=/usr/lib/?proj
+      docdir=/usr/share/?proj/doc
+      datadir=/usr/share/?proj/data
+      nimbleDir="/opt/nimble/pkgs/?c.nimblePkgName-?c.version"
+      ;;
+    "/usr/local/bin")
+      bindir=/usr/local/bin
+      configdir=/etc/?proj
+      libdir=/usr/local/lib/?proj
+      docdir=/usr/local/share/?proj/doc
+      datadir=/usr/local/share/?proj/data
+      nimbleDir="/opt/nimble/pkgs/?c.nimblePkgName-?c.version"
+      ;;
+    "/opt")
+      bindir="/opt/?proj/bin"
+      configdir="/opt/?proj/config"
+      libdir="/opt/?proj/lib"
+      docdir="/opt/?proj/doc"
+      datadir="/opt/?proj/data"
+      nimbleDir="/opt/nimble/pkgs/?c.nimblePkgName-?c.version"
+      ;;
+    *)
+      bindir="$1/?proj/bin"
+      configdir="$1/?proj/config"
+      libdir="$1/?proj/lib"
+      docdir="$1/?proj/doc"
+      datadir="$1/?proj/data"
+      nimbleDir="$1/?proj"
+      ;;
+  esac
+  echo "removing files..."
+
+#for ff in items(c.cat[fcUnixBin]):
+  #let f = ff.toUnix
+  rm -f $bindir/?f.skipRoot
+#end for
+#for ff in items(c.cat[fcConfig]):
+  #let f = ff.toUnix
+  rm -f $configdir/?f.skipRoot
+#end for
+  rm -rf $docdir
+  rm -rf $datadir
+  rm -rf $libdir
+
+  ## Nimble pkg stuff
+  #for f in items(c.cat[fcNimble]):
+    rm -f $nimbleDir/?f.toUnix
+  #end for
+  rm -f $nimbleDir/?{c.nimblePkgName}.nimble
+
+  echo "deinstallation successful"
+else
+  echo "?c.displayName deinstallation script"
+  echo "Usage: [sudo] sh deinstall.sh DIR"
+  echo "Where DIR may be:"
+  echo "  /usr/bin"
+  echo "  /usr/local/bin"
+  echo "  /opt"
+  echo "  <some other dir> (treated similar '/opt')"
+  exit 1
+fi
diff --git a/tools/niminst/inno.nimf b/tools/niminst/inno.nimf
new file mode 100644
index 000000000..ef2da8a75
--- /dev/null
+++ b/tools/niminst/inno.nimf
@@ -0,0 +1,182 @@
+#? stdtmpl | standard
+#proc generateInnoSetup(c: ConfigData): string =
+#  result = ""
+; Default Template for NimInst
+[Setup]
+AppName=$c.displayName
+AppVerName=$c.displayName $c.version
+DefaultDirName={code:GiveMeAPath|$c.displayName}
+DefaultGroupName=$c.displayName
+AllowNoIcons=yes
+LicenseFile=${expandFilename(c.license)}
+OutputBaseFilename=${c.name}_${c.version}
+Compression=lzma
+SolidCompression=yes
+PrivilegesRequired=none
+ChangesEnvironment=yes
+
+[Languages]
+Name: english; MessagesFile: compiler:Default.isl
+
+[Files] 
+  #for i in low(FileCategory)..fcWindows:
+  #  for f in items(c.cat[i]):
+Source: ${expandFilename(f).toWin}; DestDir: {app}\${splitFile(f).dir.toWin}; Flags: ignoreversion
+  #  end for
+  #end for
+
+[Icons]
+  #if c.app == appConsole:
+Name: {group}\Console for $c.displayName; Filename: {cmd}
+  #else:
+Name: {group}\$c.displayName; Filename: {app}\${c.name}.exe
+  #end if
+  #for f in items(c.cat[fcDocStart]):
+Name: {group}\Documentation; Filename: {app}\${f.toWin}
+  #end for
+Name: {group}\{cm:UninstallProgram,$c.displayName}; Filename: {uninstallexe}
+
+  #if c.binPaths.len > 0:
+[Tasks]
+Name: modifypath; Description: &Add $c.displayName to your system path (if not in path already);
+  #end if
+
+[Code]
+function GiveMeAPath(const DefaultPathName: string): string;
+begin
+  if IsAdminLoggedOn then result := ExpandConstant('{pf}')
+  else result := ExpandConstant('{userdocs}');
+  result := result + '\' + DefaultPathName;
+end;
+
+  #if c.binPaths.len > 0:
+// ----------------------------------------------------------------------------
+//
+// Inno Setup Ver:  5.2.1
+// Script Version:  1.3.1
+// Author:          Jared Breland <jbreland@legroom.net>
+// Homepage:    http://www.legroom.net/software
+//
+// Script Function:
+//  Enable modification of system path directly from Inno Setup installers
+
+function ModPathDir(): TArrayOfString;
+begin
+  setArrayLength(result, $c.binPaths.len);
+    #var i = 0
+    #for b in items(c.binPaths):
+  result[$i] := ExpandConstant('{app}') + '\${b.toWin}';
+      #inc(i)
+    #end for
+end;
+
+procedure ModPath();
+var
+  oldpath, newpath, aExecFile: String;
+  pathArr, aExecArr, pathdir: TArrayOfString;
+  i, d: Integer;
+begin
+  // Get array of new directories and act on each individually
+  pathdir := ModPathDir();
+  for d := 0 to GetArrayLength(pathdir)-1 do begin
+    // Modify WinNT path
+    if UsingWinNT() then begin
+      // Get current path, split into an array
+      RegQueryStringValue(HKEY_LOCAL_MACHINE,
+        'SYSTEM\CurrentControlSet\Control\Session Manager\Environment',
+        'Path', oldpath);
+      oldpath := oldpath + ';';
+      i := 0;
+      while (Pos(';', oldpath) > 0) do begin
+        SetArrayLength(pathArr, i+1);
+        pathArr[i] := Copy(oldpath, 0, Pos(';', oldpath)-1);
+        oldpath := Copy(oldpath, Pos(';', oldpath)+1, Length(oldpath));
+        i := i + 1;
+        // Check if current directory matches app dir
+        if pathdir[d] = pathArr[i-1] then begin
+          // if uninstalling, remove dir from path
+          if IsUninstaller() then continue
+          // if installing, abort because dir was already in path
+          else abort;
+        end;
+        // Add current directory to new path
+        if i = 1 then newpath := pathArr[i-1]
+        else newpath := newpath + ';' + pathArr[i-1];
+      end;
+      // Append app dir to path if not already included
+      if not IsUninstaller() then
+        newpath := newpath + ';' + pathdir[d];
+      // Write new path
+      RegWriteStringValue(HKEY_LOCAL_MACHINE,
+        'SYSTEM\CurrentControlSet\Control\Session Manager\Environment',
+        'Path', newpath);
+    end
+    else begin
+      // Modify Win9x path
+      // Convert to shortened dirname
+      pathdir[d] := GetShortName(pathdir[d]);
+      // If autoexec.bat exists, check if app dir already exists in path
+      aExecFile := 'C:\AUTOEXEC.BAT';
+      if FileExists(aExecFile) then begin
+        LoadStringsFromFile(aExecFile, aExecArr);
+        for i := 0 to GetArrayLength(aExecArr)-1 do begin
+          if not IsUninstaller() then begin
+            // If app dir already exists while installing, abort add
+            if (Pos(pathdir[d], aExecArr[i]) > 0) then
+              abort;
+          end
+          else begin
+            // If app dir exists and = what we originally set,
+            // then delete at uninstall
+            if aExecArr[i] = 'SET PATH=%PATH%;' + pathdir[d] then
+              aExecArr[i] := '';
+          end;
+        end;
+      end;
+      // If app dir not found, or autoexec.bat didn't exist, then
+      // (create and) append to current path
+      if not IsUninstaller() then begin
+        SaveStringToFile(aExecFile, #13#10 + 'SET PATH=%PATH%;' + pathdir[d],
+                         True);
+      end
+      else begin
+        // If uninstalling, write the full autoexec out
+        SaveStringsToFile(aExecFile, aExecArr, False);
+      end;
+    end;
+
+    // Write file to flag modifypath was selected
+    // Workaround since IsTaskSelected() cannot be called at uninstall and
+    // AppName and AppId cannot be "read" in Code section
+    if not IsUninstaller() then
+      SaveStringToFile(ExpandConstant('{app}') + '\uninsTasks.txt',
+                       WizardSelectedTasks(False), False);
+  end;
+end;
+
+procedure CurStepChanged(CurStep: TSetupStep);
+begin
+  if CurStep = ssPostInstall then begin
+    if IsTaskSelected('modifypath') then
+      ModPath();
+  end
+end;
+
+procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);
+var
+  appdir, selectedTasks: String;
+begin
+  appdir := ExpandConstant('{app}');
+  if CurUninstallStep = usUninstall then begin
+    if LoadStringFromFile(appdir + '\uninsTasks.txt', selectedTasks) then
+      if Pos('modifypath', selectedTasks) > 0 then
+        ModPath();
+    DeleteFile(appdir + '\uninsTasks.txt')
+  end;
+end;
+
+function NeedRestart(): Boolean;
+begin
+  result := IsTaskSelected('modifypath') and not UsingWinNT()
+end;
+  #end if
diff --git a/tools/niminst/install.nimf b/tools/niminst/install.nimf
new file mode 100644
index 000000000..75ff9ce11
--- /dev/null
+++ b/tools/niminst/install.nimf
@@ -0,0 +1,138 @@
+#? stdtmpl(subsChar = '?') | standard
+#proc generateInstallScript(c: ConfigData): string =
+#  result = "#!/bin/sh\n# Generated by niminst\n"
+#  var proj = c.name.toLowerAscii
+
+set -e
+
+if [ $# -eq 1 ] ; then
+#if c.cat[fcUnixBin].len > 0:
+  if [ -f "?{c.cat[fcUnixBin][0].toUnix}" ]
+  then
+    echo "?c.displayName build detected"
+  else
+    echo "Please build ?c.displayName before installing it"
+    exit 1
+  fi
+#end if
+  case $1 in
+    "--help"|"-h"|"help"|"h")
+      echo "?c.displayName installation script"
+      echo "Usage: [sudo] [env DESTDIR=...] sh install.sh DIR"
+      echo "Where DIR may be:"
+      echo "  /usr/bin"
+      echo "  /usr/local/bin"
+      echo "  /opt"
+      echo "  <some other dir> (treated similar to '/opt')"
+      echo "To deinstall, use the command:"
+      echo "sh deinstall.sh DIR"
+      exit 1
+      ;;
+    "/usr/bin")
+      bindir=$1
+      configdir="/etc/?proj"
+      libdir="/usr/lib/?proj"
+      docdir="/usr/share/?proj/doc"
+      datadir="/usr/share/?proj/data"
+      nimbleDir="/opt/nimble/pkgs/?c.nimblePkgName-?c.version"
+      ;;
+    "/usr/local/bin")
+      bindir=$1
+      configdir="/etc/?proj"
+      libdir="/usr/local/lib/?proj"
+      docdir="/usr/local/share/?proj/doc"
+      datadir="/usr/local/share/?proj/data"
+      nimbleDir="/opt/nimble/pkgs/?c.nimblePkgName-?c.version"
+      ;;
+    "/opt")
+      bindir="/opt/?proj/bin"
+      configdir="/opt/?proj/config"
+      libdir="/opt/?proj/lib"
+      docdir="/opt/?proj/doc"
+      datadir="/opt/?proj/data"
+      nimbleDir="/opt/nimble/pkgs/?c.nimblePkgName-?c.version"
+      ;;
+    *)
+      bindir="$1/?proj/bin"
+      configdir="$1/?proj/config"
+      libdir="$1/?proj/lib"
+      docdir="$1/?proj/doc"
+      datadir="$1/?proj/data"
+      nimbleDir="$1/?proj"
+      ;;
+  esac
+
+  bindir="${DESTDIR}${bindir}"
+  configdir="${DESTDIR}${configdir}"
+  libdir="${DESTDIR}${libdir}"
+  docdir="${DESTDIR}${docdir}"
+  datadir="${DESTDIR}${datadir}"
+  nimbleDir="${DESTDIR}${nimbleDir}"
+
+  mkdir -p "$bindir"
+  mkdir -p "$configdir"
+  mkdir -p "$libdir"
+  mkdir -p "$docdir"
+  mkdir -p "$datadir"
+  mkdir -p "$nimbleDir"
+  echo "copying files..."
+#var createdDirs = newStringTable()
+#for cat in {fcConfig..fcLib, fcNimble}:
+#  for f in items(c.cat[cat]):
+#    var mk = splitFile(f.skipRoot).dir
+#    if cat != fcNimble:
+#      mk = unixDirVars[cat] & "/" & mk
+#    else:
+#      mk = "$nimbleDir" / splitFile(f).dir
+#    end if
+#    if mk.len > 0 and not createdDirs.hasKey(mk):
+#      createdDirs[mk] = "true"
+  mkdir -p "?{mk.toUnix}"
+#    end if
+#  end for
+#end for
+
+#for f in items(c.cat[fcUnixBin]):
+  cp "?f.toUnix" "$bindir/?f.skipRoot.toUnix"
+  chmod 755 "$bindir/?f.skipRoot.toUnix"
+#end for
+#for f in items(c.cat[fcConfig]):
+  cp "?f.toUnix" "$configdir/?f.skipRoot.toUnix"
+  chmod 644 "$configdir/?f.skipRoot.toUnix"
+#end for
+#for f in items(c.cat[fcData]):
+  if [ -f "?f.toUnix" ]; then
+    cp "?f.toUnix" "$datadir/?f.skipRoot.toUnix"
+    chmod 644 "$datadir/?f.skipRoot.toUnix"
+  fi
+#end for
+#for f in items(c.cat[fcDoc]):
+  if [ -f "?f.toUnix" ]; then
+    cp "?f.toUnix" "$docdir/?f.skipRoot.toUnix"
+    chmod 644 "$docdir/?f.skipRoot.toUnix"
+  fi
+#end for
+#for f in items(c.cat[fcLib]):
+  cp "?f.toUnix" "$libdir/?f.skipRoot.toUnix"
+  chmod 644 "$libdir/?f.skipRoot.toUnix"
+#end for
+#for f in items(c.cat[fcNimble]):
+  cp "?f.toUnix" "$nimbleDir/?f.toUnix"
+  chmod 644 "$nimbleDir/?f.toUnix"
+#end for
+cp "?{c.nimblePkgName}.nimble" "$nimbleDir/?{c.nimblePkgName}.nimble"
+chmod 644 "$nimbleDir/?{c.nimblePkgName}.nimble"
+
+  echo "installation successful"
+else
+  echo "?c.displayName installation script"
+  echo "Usage: [sudo] [env DESTDIR=...] sh install.sh DIR"
+  echo "Where DIR may be:"
+  echo "  /usr/bin"
+  echo "  /usr/local/bin"
+  echo "  /opt"
+  echo "  <some other dir> (treated similar to '/opt')"
+  echo "To deinstall, use the command:"
+  echo "sh deinstall.sh DIR"
+  exit 1
+fi
diff --git a/tools/niminst/makefile.nimf b/tools/niminst/makefile.nimf
new file mode 100644
index 000000000..002bc0592
--- /dev/null
+++ b/tools/niminst/makefile.nimf
@@ -0,0 +1,219 @@
+#? stdtmpl(subsChar='?') | standard
+#proc generateMakefile(c: ConfigData): string =
+#  result = "# Generated from niminst\n" &
+#           "# Template is in tools/niminst/makefile.nimf\n" &
+#           "# To regenerate run ``niminst csource`` or ``koch csource``\n"
+
+CC ??= gcc
+CFLAGS += -Ic_code ?{c.ccompiler.flags}
+LDFLAGS += ?{c.linker.flags}
+binDir = ?{firstBinPath(c).toUnix}
+
+koch := $(shell sh -c 'test -s ../koch.nim && echo "yes"')
+ifeq ($(koch),yes)
+  binDir = ../bin
+endif
+
+target := ?{"$(binDir)/" & toLowerAscii(c.name)}
+
+
+ucpu := $(shell sh -c 'uname -m | tr "[:upper:]" "[:lower:]"')
+ifeq ($(OS),Windows_NT)
+  uos := windows
+else
+  uos := $(shell sh -c 'uname | tr "[:upper:]" "[:lower:]"')
+endif
+
+ifeq ($(uos),linux)
+  myos = linux
+  ## add -lrt to avoid "undefined reference to `clock_gettime'" with glibc<2.17
+  LDFLAGS += -ldl -lm -lrt
+endif
+ifeq ($(uos),dragonfly)
+  myos = freebsd
+  LDFLAGS += -lm
+endif
+ifeq ($(uos),freebsd)
+  myos= freebsd
+  CC = clang
+  LDFLAGS += -lm
+endif
+ifeq ($(uos),openbsd)
+  myos = openbsd
+  LDFLAGS += -lm
+endif
+ifeq ($(uos),netbsd)
+  myos = netbsd
+  LDFLAGS += -lm
+  ucpu = $(shell sh -c 'uname -p')
+endif
+ifeq ($(uos),darwin)
+  myos = macosx
+  CC = clang
+  LDFLAGS += -ldl -lm
+  ifeq ($(HOSTTYPE),x86_64)
+    ucpu = amd64
+  endif
+endif
+ifeq ($(uos),aix)
+  myos = aix
+  LDFLAGS += -dl -lm
+endif
+ifeq ($(uos),solaris)
+  myos = solaris
+  LDFLAGS += -ldl -lm -lsocket -lnsl
+endif
+ifeq ($(uos),sun)
+  myos = solaris
+  LDFLAGS += -ldl -lm -lsocket -lnsl
+endif
+ifeq ($(uos),haiku)
+  myos = haiku
+endif
+ifeq ($(uos),windows)
+  myos = windows
+  target := $(target).exe
+endif
+ifndef myos
+  $(error unknown operating system: $(uos))
+endif
+
+ifeq ($(ucpu),i386)
+  mycpu = i386
+endif
+ifeq ($(ucpu),i486)
+  mycpu = i386
+endif
+ifeq ($(ucpu),i586)
+  mycpu = i386
+endif
+ifeq ($(ucpu),i686)
+  mycpu = i386
+endif
+ifeq ($(ucpu),bepc)
+  mycpu = i386
+endif
+ifeq ($(ucpu),i86pc)
+  mycpu = i386
+endif
+ifeq ($(ucpu),amd64)
+  mycpu = amd64
+endif
+ifeq ($(ucpu),x86-64)
+  mycpu = amd64
+endif
+ifeq ($(ucpu),x86_64)
+  mycpu = amd64
+endif
+ifeq ($(ucpu),parisc64)
+  mycpu = hppa
+endif
+ifeq ($(ucpu),s390x)
+  mycpu = s390x
+endif
+ifeq ($(ucpu),sparc)
+  mycpu = sparc
+endif
+ifeq ($(ucpu),sparc64)
+  mycpu = sparc64
+endif
+ifeq ($(ucpu),sun)
+  mycpu = sparc
+endif
+ifeq ($(ucpu),ppc64le)
+  mycpu = powerpc64el
+endif
+ifeq ($(ucpu),ppc64)
+  mycpu = powerpc64
+  ifeq ($(myos),linux)
+    CFLAGS += -m64
+    LDFLAGS += -m64
+  endif
+endif
+ifeq ($(ucpu),powerpc)
+  mycpu = powerpc
+  ifeq ($(myos),freebsd)
+    mycpu = $(shell sh -c 'uname -p | tr "[:upper:]" "[:lower:]"')
+    CFLAGS += -m64
+    LDFLAGS += -m64
+    ifeq ($(mycpu),powerpc64le)
+      mycpu = powerpc64el
+    endif
+  endif
+endif
+ifeq ($(ucpu),ppc)
+   mycpu = powerpc
+endif
+ifneq (,$(filter $(ucpu), mips mips64))
+  mycpu = $(shell /bin/sh -c '"$(CC)" -dumpmachine | sed "s/-.*//"')
+  ifeq (,$(filter $(mycpu), mips mipsel mips64 mips64el))
+    $(error unknown MIPS target: $(mycpu))
+  endif
+endif
+ifeq ($(ucpu),arm)
+  mycpu = arm
+endif
+ifeq ($(ucpu),armeb)
+  mycpu = arm
+endif
+ifeq ($(ucpu),armel)
+  mycpu = arm
+endif
+ifeq ($(ucpu),armv6l)
+  mycpu = arm
+endif
+ifeq ($(ucpu),armv7l)
+  mycpu = arm
+endif
+ifeq ($(ucpu),armv7hl)
+  mycpu = arm
+endif
+ifeq ($(ucpu),armv8l)
+  mycpu = arm
+endif
+ifeq ($(ucpu),aarch64)
+  mycpu = arm64
+endif
+ifeq ($(ucpu),arm64)
+  mycpu = arm64
+endif
+ifeq ($(ucpu),riscv64)
+  mycpu = riscv64
+endif
+ifeq ($(ucpu),e2k)
+  mycpu = e2k
+endif
+ifeq ($(ucpu),loongarch64)
+  mycpu = loongarch64
+endif
+ifndef mycpu
+  $(error unknown CPU architecture: $(ucpu) See makefile.nimf)
+endif
+
+# for osA in 1..c.oses.len:
+ifeq ($(myos),?{c.oses[osA-1]})
+#   for cpuA in 1..c.cpus.len:
+  ifeq ($(mycpu),?{c.cpus[cpuA-1]})
+#     var oFiles = ""
+#     for ff in c.cfiles[osA][cpuA].items:
+#       oFiles.add(" " & changeFileExt(ff.toUnix, "o"))
+#     end for
+    oFiles =?oFiles
+  endif
+#   end for
+endif
+# end for
+
+ifeq ($(strip $(oFiles)),)
+  $(error no C code generated for: [$(myos): $(mycpu)])
+endif
+
+$(target): $(oFiles)
+	@mkdir -p $(binDir)
+	$(CC) -o $@ $^ $(LDFLAGS)
+	@echo "SUCCESS"
+
+.PHONY: clean
+
+clean:
+	rm -f $(oFiles) ?{"$(binDir)/" & toLowerAscii(c.name)}
diff --git a/tools/niminst/nim-file.ico b/tools/niminst/nim-file.ico
new file mode 100644
index 000000000..0685fedcb
--- /dev/null
+++ b/tools/niminst/nim-file.ico
Binary files differdiff --git a/tools/niminst/niminst.nim b/tools/niminst/niminst.nim
new file mode 100644
index 000000000..40ee79814
--- /dev/null
+++ b/tools/niminst/niminst.nim
@@ -0,0 +1,751 @@
+#
+#
+#        The Nim Installation Generator
+#        (c) Copyright 2015 Andreas Rumpf
+#
+#    See the file "copying.txt", included in this
+#    distribution, for details about the copyright.
+#
+
+import
+  os, strutils, parseopt, parsecfg, strtabs, streams, debcreation
+
+import ../../dist/checksums/src/checksums/sha1
+
+when defined(nimPreviewSlimSystem):
+  import std/syncio
+
+when not defined(nimHasEffectsOf):
+  {.pragma: effectsOf.}
+
+const
+  maxOS = 20 # max number of OSes
+  maxCPU = 20 # max number of CPUs
+  buildShFile = "build.sh"
+  buildBatFile = "build.bat"
+  buildBatFile32 = "build32.bat"
+  buildBatFile64 = "build64.bat"
+  makeFile = "makefile"
+  installShFile = "install.sh"
+  deinstallShFile = "deinstall.sh"
+
+type
+  AppType = enum appConsole, appGUI
+  Action = enum
+    actionNone,   # action not yet known
+    actionCSource # action: create C sources
+    actionInno,   # action: create Inno Setup installer
+    actionNsis,   # action: create NSIS installer
+    actionScripts # action: create install and deinstall scripts
+    actionZip     # action: create zip file
+    actionXz,     # action: create xz file
+    actionDeb     # action: prepare deb package
+
+  FileCategory = enum
+    fcWinBin,     # binaries for Windows
+    fcConfig,     # configuration files
+    fcData,       # data files
+    fcDoc,        # documentation files
+    fcLib,        # library files
+    fcOther,      # other files; will not be copied on UNIX
+    fcWindows,    # files only for Windows
+    fcUnix,       # files only for Unix; must be after ``fcWindows``
+    fcUnixBin,    # binaries for Unix
+    fcDocStart,   # links to documentation for Windows installer
+    fcNimble      # nimble package files to copy to /opt/nimble/pkgs/pkg-ver
+
+  ConfigData = object of RootObj
+    actions: set[Action]
+    cat: array[FileCategory, seq[string]]
+    binPaths, authors, oses, cpus, downloads: seq[string]
+    cfiles: array[1..maxOS, array[1..maxCPU, seq[string]]]
+    platforms: array[1..maxOS, array[1..maxCPU, bool]]
+    ccompiler, linker, innosetup, nsisSetup: tuple[path, flags: string]
+    name, displayName, version, description, license, infile, outdir: string
+    mainfile, libpath: string
+    innoSetupFlag, installScript, uninstallScript: bool
+    explicitPlatforms: bool
+    vars: StringTableRef
+    app: AppType
+    nimArgs: string
+    debOpts: TDebOptions
+    nimblePkgName: string
+
+const
+  unixDirVars: array[fcConfig..fcLib, string] = [
+    "$configdir", "$datadir", "$docdir", "$libdir"
+  ]
+
+func iniConfigData(c: var ConfigData) =
+  c.actions = {}
+  for i in low(FileCategory)..high(FileCategory): c.cat[i] = @[]
+  c.binPaths = @[]
+  c.authors = @[]
+  c.oses = @[]
+  c.cpus = @[]
+  c.downloads = @[]
+  c.ccompiler = ("", "")
+  c.linker = ("", "")
+  c.innosetup = ("", "")
+  c.nsisSetup = ("", "")
+  c.name = ""
+  c.displayName = ""
+  c.version = ""
+  c.description = ""
+  c.license = ""
+  c.infile = ""
+  c.mainfile = ""
+  c.outdir = ""
+  c.nimArgs = ""
+  c.libpath = ""
+  c.innoSetupFlag = false
+  c.installScript = false
+  c.uninstallScript = false
+  c.vars = newStringTable(modeStyleInsensitive)
+
+  c.debOpts.buildDepends = ""
+  c.debOpts.pkgDepends = ""
+  c.debOpts.shortDesc = ""
+  c.debOpts.licenses = @[]
+
+func firstBinPath(c: ConfigData): string =
+  if c.binPaths.len > 0: result = c.binPaths[0]
+  else: result = ""
+
+func `\`(a, b: string): string =
+  result = if a.len == 0: b else: a & '\\' & b
+
+template toUnix(s: string): string = s.replace('\\', '/')
+template toWin(s: string): string = s.replace('/', '\\')
+
+func skipRoot(f: string): string =
+  # "abc/def/xyz" --> "def/xyz"
+  var i = 0
+  result = ""
+  for component in split(f, {DirSep, AltSep}):
+    if i > 0: result = result / component
+    inc i
+  if result.len == 0: result = f
+
+include "inno.nimf"
+include "nsis.nimf"
+include "buildsh.nimf"
+include "makefile.nimf"
+include "buildbat.nimf"
+include "install.nimf"
+include "deinstall.nimf"
+
+# ------------------------- configuration file -------------------------------
+
+const
+  Version = "1.0"
+  Usage = "niminst - Nim Installation Generator Version " & Version & """
+
+  (c) 2015 Andreas Rumpf
+Usage:
+  niminst [options] command[;command2...] ini-file[.ini] [compile_options]
+Command:
+  csource             build C source code for source based installations
+  scripts             build install and deinstall scripts
+  zip                 build the ZIP file
+  inno                build the Inno Setup installer
+  nsis                build the NSIS Setup installer
+  deb                 create files for debhelper
+Options:
+  -o, --output:dir    set the output directory
+  -m, --main:file     set the main nim file, by default ini-file with .nim
+                      extension
+  --var:name=value    set the value of a variable
+  -h, --help          shows this help
+  -v, --version       shows the version
+Compile_options:
+  will be passed to the Nim compiler
+"""
+
+proc parseCmdLine(c: var ConfigData) =
+  var p = initOptParser()
+  while true:
+    next(p)
+    var kind = p.kind
+    var key = p.key
+    var val = p.val
+    case kind
+    of cmdArgument:
+      if c.actions == {}:
+        for a in split(normalize(key), {';', ','}):
+          case a
+          of "csource": incl(c.actions, actionCSource)
+          of "scripts": incl(c.actions, actionScripts)
+          of "zip": incl(c.actions, actionZip)
+          of "xz": incl(c.actions, actionXz)
+          of "inno": incl(c.actions, actionInno)
+          of "nsis": incl(c.actions, actionNsis)
+          of "deb": incl(c.actions, actionDeb)
+          else: quit(Usage)
+      else:
+        c.infile = addFileExt(key, "ini")
+        c.nimArgs = cmdLineRest(p)
+        break
+    of cmdLongOption, cmdShortOption:
+      case normalize(key)
+      of "help", "h":
+        stdout.write(Usage)
+        quit(0)
+      of "version", "v":
+        stdout.write(Version & "\n")
+        quit(0)
+      of "o", "output": c.outdir = val
+      of "m", "main": c.mainfile = changeFileExt(val, "nim")
+      of "var":
+        var idx = val.find('=')
+        if idx < 0: quit("invalid command line")
+        c.vars[substr(val, 0, idx-1)] = substr(val, idx+1)
+      else: quit(Usage)
+    of cmdEnd: break
+  if c.infile.len == 0: quit(Usage)
+  if c.mainfile.len == 0: c.mainfile = changeFileExt(c.infile, "nim")
+
+proc eqT(a, b: string; t: proc (a: char): char {.nimcall.}): bool {.effectsOf: t.} =
+  ## equality under a transformation ``t``. candidate for the stdlib?
+  var i = 0
+  var j = 0
+  while i < a.len and j < b.len:
+    let aa = t a[i]
+    let bb = t b[j]
+    if aa == '\0':
+      inc i
+      if bb == '\0': inc j
+    elif bb == '\0': inc j
+    else:
+      if aa != bb: return false
+      inc i
+      inc j
+  result = i >= a.len and j >= b.len
+
+func tPath(c: char): char =
+  if c == '\\': '/'
+  else: c
+
+func ignoreFile(f, explicit: string, allowHtml: bool): bool =
+  let (_, name, ext) = splitFile(f)
+  let html = if not allowHtml: ".html" else: ""
+  result = (ext in ["", ".exe", ".idx", ".o", ".obj", ".dylib"] or
+            ext == html or name[0] == '.') and not eqT(f, explicit, tPath)
+
+proc walkDirRecursively(s: var seq[string], root, explicit: string,
+                        allowHtml: bool) =
+  let tail = splitPath(root).tail
+  if tail == "nimcache" or tail[0] == '.':
+    return
+  let allowHtml = allowHtml or tail == "doc"
+  for k, f in walkDir(root):
+    if f[0] == '.' and root[0] != '.':
+      discard "skip .git directories etc"
+    else:
+      case k
+      of pcFile, pcLinkToFile:
+        if not ignoreFile(f, explicit, allowHtml):
+          add(s, unixToNativePath(f))
+      of pcDir:
+        walkDirRecursively(s, f, explicit, allowHtml)
+      of pcLinkToDir: discard
+
+proc addFiles(s: var seq[string], patterns: seq[string]) =
+  for p in items(patterns):
+    if dirExists(p):
+      walkDirRecursively(s, p, p, false)
+    else:
+      var i = 0
+      for f in walkPattern(p):
+        if dirExists(f):
+          walkDirRecursively(s, f, p, false)
+        elif not ignoreFile(f, p, false):
+          add(s, unixToNativePath(f))
+          inc(i)
+      if i == 0: echo("[Warning] No file found that matches: " & p)
+
+proc pathFlags(p: var CfgParser, k, v: string,
+               t: var tuple[path, flags: string]) =
+  case normalize(k)
+  of "path": t.path = v
+  of "flags": t.flags = v
+  else: quit(errorStr(p, "unknown variable: " & k))
+
+proc filesOnly(p: var CfgParser, k, v: string, dest: var seq[string]) =
+  case normalize(k)
+  of "files": addFiles(dest, split(v, {';'}))
+  else: quit(errorStr(p, "unknown variable: " & k))
+
+proc yesno(p: var CfgParser, v: string): bool =
+  case normalize(v)
+  of "yes", "y", "on", "true":
+    result = true
+  of "no", "n", "off", "false":
+    result = false
+  else: quit(errorStr(p, "unknown value; use: yes|no"))
+
+func incl(s: var seq[string], x: string): int =
+  for i in 0 ..< s.len:
+    if cmpIgnoreStyle(s[i], x) == 0: return i
+  s.add(x)
+  result = s.len-1
+
+func platforms(c: var ConfigData, v: string) =
+  for line in splitLines(v):
+    let p = line.find(": ")
+    if p <= 1: continue
+    let os = line.substr(0, p-1).strip
+    let cpus = line.substr(p+1).strip
+    c.oses.add(os)
+    for cpu in cpus.split(';'):
+      let cpuIdx = c.cpus.incl(cpu)
+      c.platforms[c.oses.len][cpuIdx+1] = true
+
+proc parseIniFile(c: var ConfigData) =
+  var
+    p: CfgParser
+    section = ""
+    hasCpuOs = false
+  var input = newFileStream(c.infile, fmRead)
+  if input != nil:
+    open(p, input, c.infile)
+    while true:
+      var k = next(p)
+      case k.kind
+      of cfgEof: break
+      of cfgSectionStart:
+        section = normalize(k.section)
+      of cfgKeyValuePair:
+        var v = `%`(k.value, c.vars, {useEnvironment, useEmpty})
+        c.vars[k.key] = v
+
+        case section
+        of "project":
+          case normalize(k.key)
+          of "name": c.name = v
+          of "displayname": c.displayName = v
+          of "version": c.version = v
+          of "os":
+            c.oses = split(v, {';'})
+            hasCpuOs = true
+            if c.explicitPlatforms:
+              quit(errorStr(p, "you cannot have both 'platforms' and 'os'"))
+          of "cpu":
+            c.cpus = split(v, {';'})
+            hasCpuOs = true
+            if c.explicitPlatforms:
+              quit(errorStr(p, "you cannot have both 'platforms' and 'cpu'"))
+          of "platforms":
+            platforms(c, v)
+            c.explicitPlatforms = true
+            if hasCpuOs:
+              quit(errorStr(p, "you cannot have both 'platforms' and 'os'"))
+          of "authors": c.authors = split(v, {';'})
+          of "description": c.description = v
+          of "app":
+            case normalize(v)
+            of "console": c.app = appConsole
+            of "gui": c.app = appGUI
+            else: quit(errorStr(p, "expected: console or gui"))
+          of "license": c.license = unixToNativePath(k.value)
+          else: quit(errorStr(p, "unknown variable: " & k.key))
+        of "var": discard
+        of "winbin": filesOnly(p, k.key, v, c.cat[fcWinBin])
+        of "config": filesOnly(p, k.key, v, c.cat[fcConfig])
+        of "data": filesOnly(p, k.key, v, c.cat[fcData])
+        of "documentation":
+          case normalize(k.key)
+          of "files": addFiles(c.cat[fcDoc], split(v, {';'}))
+          of "start": addFiles(c.cat[fcDocStart], split(v, {';'}))
+          else: quit(errorStr(p, "unknown variable: " & k.key))
+        of "lib": filesOnly(p, k.key, v, c.cat[fcLib])
+        of "other": filesOnly(p, k.key, v, c.cat[fcOther])
+        of "windows":
+          case normalize(k.key)
+          of "files": addFiles(c.cat[fcWindows], split(v, {';'}))
+          of "binpath": c.binPaths = split(v, {';'})
+          of "innosetup": c.innoSetupFlag = yesno(p, v)
+          of "download": c.downloads.add(v)
+          else: quit(errorStr(p, "unknown variable: " & k.key))
+        of "unix":
+          case normalize(k.key)
+          of "files": addFiles(c.cat[fcUnix], split(v, {';'}))
+          of "installscript": c.installScript = yesno(p, v)
+          of "uninstallscript": c.uninstallScript = yesno(p, v)
+          else: quit(errorStr(p, "unknown variable: " & k.key))
+        of "unixbin": filesOnly(p, k.key, v, c.cat[fcUnixBin])
+        of "innosetup": pathFlags(p, k.key, v, c.innosetup)
+        of "nsis": pathFlags(p, k.key, v, c.nsisSetup)
+        of "ccompiler": pathFlags(p, k.key, v, c.ccompiler)
+        of "linker": pathFlags(p, k.key, v, c.linker)
+        of "deb":
+          case normalize(k.key)
+          of "builddepends":
+            c.debOpts.buildDepends = v
+          of "packagedepends", "pkgdepends":
+            c.debOpts.pkgDepends = v
+          of "shortdesc":
+            c.debOpts.shortDesc = v
+          of "licenses":
+            # file,license;file,license;
+            var i = 0
+            var file = ""
+            var license = ""
+            var afterComma = false
+            while i < v.len():
+              case v[i]
+              of ',':
+                afterComma = true
+              of ';':
+                if file == "" or license == "":
+                  quit(errorStr(p, "Invalid `licenses` key."))
+                c.debOpts.licenses.add((file, license))
+                afterComma = false
+                file = ""
+                license = ""
+              else:
+                if afterComma: license.add(v[i])
+                else: file.add(v[i])
+              inc(i)
+          else: quit(errorStr(p, "unknown variable: " & k.key))
+        of "nimble":
+          case normalize(k.key)
+          of "pkgname":
+            c.nimblePkgName = v
+          of "pkgfiles":
+            addFiles(c.cat[fcNimble], split(v, {';'}))
+          else:
+            quit(errorStr(p, "invalid key: " & k.key))
+        else: quit(errorStr(p, "invalid section: " & section))
+
+      of cfgOption: quit(errorStr(p, "syntax error"))
+      of cfgError: quit(errorStr(p, k.msg))
+    close(p)
+    if c.name.len == 0: c.name = changeFileExt(extractFilename(c.mainfile), "")
+    if c.displayName.len == 0: c.displayName = c.name
+  else:
+    quit("cannot open: " & c.infile)
+
+# ------------------------- generate source based installation ---------------
+
+proc readCFiles(c: var ConfigData, osA, cpuA: int) =
+  var p: CfgParser
+  var f = splitFile(c.infile).dir / "mapping.txt"
+  c.cfiles[osA][cpuA] = @[]
+  var input = newFileStream(f, fmRead)
+  var section = ""
+  if input != nil:
+    open(p, input, f)
+    while true:
+      var k = next(p)
+      case k.kind
+      of cfgEof: break
+      of cfgSectionStart:
+        section = normalize(k.section)
+      of cfgKeyValuePair:
+        case section
+        of "ccompiler": pathFlags(p, k.key, k.value, c.ccompiler)
+        of "linker":
+          pathFlags(p, k.key, k.value, c.linker)
+          # HACK: we conditionally add ``-lm -ldl``, so remove them from the
+          # linker flags:
+          c.linker.flags = c.linker.flags.replaceWord("-lm").replaceWord(
+                           "-ldl").replaceWord("-lroot").replaceWord(
+                           "-lnetwork").strip
+        else:
+          if cmpIgnoreStyle(k.key, "libpath") == 0:
+            c.libpath = k.value
+      of cfgOption:
+        if section == "cfiles" and cmpIgnoreStyle(k.key, "file") == 0:
+          add(c.cfiles[osA][cpuA], k.value)
+      of cfgError: quit(errorStr(p, k.msg))
+    close(p)
+  else:
+    quit("Cannot open: " & f)
+
+func buildDir(os, cpu: int): string =
+  "c_code" / ($os & "_" & $cpu)
+
+func getOutputDir(c: var ConfigData): string =
+  if c.outdir.len > 0: c.outdir else: "build"
+
+proc writeFile(filename, content, newline: string) =
+  var f: File
+  if open(f, filename, fmWrite):
+    for x in splitLines(content):
+      write(f, x)
+      write(f, newline)
+    close(f)
+  else:
+    quit("Cannot open for writing: " & filename)
+
+proc deduplicateFiles(c: var ConfigData) =
+  var tab = newStringTable()
+  let build = getOutputDir(c)
+  for osA in countup(1, c.oses.len):
+    for cpuA in countup(1, c.cpus.len):
+      if c.explicitPlatforms and not c.platforms[osA][cpuA]: continue
+      for dup in mitems(c.cfiles[osA][cpuA]):
+        let key = $secureHashFile(build / dup)
+        let val = buildDir(osA, cpuA) / extractFilename(dup)
+        let orig = tab.getOrDefault(key)
+        if orig.len > 0:
+          # file is identical, so delete duplicate:
+          removeFile(dup)
+          dup = orig
+        else:
+          tab[key] = val
+
+proc writeInstallScripts(c: var ConfigData) =
+  if c.installScript:
+    writeFile(installShFile, generateInstallScript(c), "\10")
+    inclFilePermissions(installShFile, {fpUserExec, fpGroupExec, fpOthersExec})
+  if c.uninstallScript:
+    writeFile(deinstallShFile, generateDeinstallScript(c), "\10")
+    inclFilePermissions(deinstallShFile, {fpUserExec, fpGroupExec, fpOthersExec})
+
+template gatherFiles(fun, libpath, outDir) =
+  block:
+    template copySrc(src) =
+      let dst = outDir / extractFilename(src)
+      when false: echo (dst, dst)
+      fun(src, dst)
+
+    for f in walkFiles(libpath / "lib/*.h"): copySrc(f)
+    # commenting out for now, see discussion in https://github.com/nim-lang/Nim/pull/13413
+    # copySrc(libpath / "lib/wrappers/linenoise/linenoise.h")
+
+proc exe(f: string): string =
+  result = addFileExt(f, ExeExt)
+  when defined(windows):
+    result = result.replace('/','\\')
+
+proc findNim(): string =
+  let nim = "nim".exe
+  result = quoteShell("bin" / nim)
+  if not fileExists(result):
+    result = "nim"
+
+proc srcdist(c: var ConfigData) =
+  let cCodeDir = getOutputDir(c) / "c_code"
+  if not dirExists(cCodeDir): createDir(cCodeDir)
+  gatherFiles(copyFile, c.libpath, cCodeDir)
+  var winIndex = -1
+  var intel32Index = -1
+  var intel64Index = -1
+  for osA in 1..c.oses.len:
+    let osname = c.oses[osA-1]
+    if osname.cmpIgnoreStyle("windows") == 0: winIndex = osA
+    for cpuA in 1..c.cpus.len:
+      if c.explicitPlatforms and not c.platforms[osA][cpuA]: continue
+      let cpuname = c.cpus[cpuA-1]
+      if cpuname.cmpIgnoreStyle("i386") == 0: intel32Index = cpuA
+      elif cpuname.cmpIgnoreStyle("amd64") == 0: intel64Index = cpuA
+      var dir = getOutputDir(c) / buildDir(osA, cpuA)
+      if dirExists(dir): removeDir(dir)
+      createDir(dir)
+      var cmd = ("$# compile -f --incremental:off --compileonly " &
+                 "--gen_mapping --cc:gcc --skipUserCfg" &
+                 " --os:$# --cpu:$# $# $#") %
+                 [findNim(), osname, cpuname, c.nimArgs, c.mainfile]
+      echo(cmd)
+      if execShellCmd(cmd) != 0:
+        quit("Error: call to nim compiler failed")
+      readCFiles(c, osA, cpuA)
+      for i in 0 .. c.cfiles[osA][cpuA].len-1:
+        let dest = dir / extractFilename(c.cfiles[osA][cpuA][i])
+        let relDest = buildDir(osA, cpuA) / extractFilename(c.cfiles[osA][cpuA][i])
+        copyFile(dest=dest, source=c.cfiles[osA][cpuA][i])
+        c.cfiles[osA][cpuA][i] = relDest
+  # second pass: remove duplicate files
+  deduplicateFiles(c)
+  writeFile(getOutputDir(c) / buildShFile, generateBuildShellScript(c), "\10")
+  inclFilePermissions(getOutputDir(c) / buildShFile, {fpUserExec, fpGroupExec, fpOthersExec})
+  writeFile(getOutputDir(c) / makeFile, generateMakefile(c), "\10")
+  if winIndex >= 0:
+    if intel32Index >= 0 or intel64Index >= 0:
+      writeFile(getOutputDir(c) / buildBatFile,
+                generateBuildBatchScript(c, winIndex, intel32Index, intel64Index), "\13\10")
+    if intel32Index >= 0:
+      writeFile(getOutputDir(c) / buildBatFile32, "SET ARCH=32\nCALL build.bat\n")
+    if intel64Index >= 0:
+      writeFile(getOutputDir(c) / buildBatFile64, "SET ARCH=64\nCALL build.bat\n")
+  writeInstallScripts(c)
+
+# --------------------- generate inno setup -----------------------------------
+proc setupDist(c: var ConfigData) =
+  let scrpt = generateInnoSetup(c)
+  let n = "build" / "install_$#_$#.iss" % [toLowerAscii(c.name), c.version]
+  writeFile(n, scrpt, "\13\10")
+  when defined(windows):
+    if c.innosetup.path.len == 0:
+      c.innosetup.path = "iscc.exe"
+    let outcmd = if c.outdir.len == 0: "build" else: c.outdir
+    let cmd = "$# $# /O$# $#" % [quoteShell(c.innosetup.path),
+                                 c.innosetup.flags, outcmd, n]
+    echo(cmd)
+    if execShellCmd(cmd) == 0:
+      removeFile(n)
+    else:
+      quit("External program failed")
+
+# --------------------- generate NSIS setup -----------------------------------
+proc setupDist2(c: var ConfigData) =
+  let scrpt = generateNsisSetup(c)
+  let n = "build" / "install_$#_$#.nsi" % [toLowerAscii(c.name), c.version]
+  writeFile(n, scrpt, "\13\10")
+  when defined(windows):
+    if c.nsisSetup.path.len == 0:
+      c.nsisSetup.path = "makensis.exe"
+    let outcmd = if c.outdir.len == 0: "build" else: c.outdir
+    let cmd = "$# $# /O$# $#" % [quoteShell(c.nsisSetup.path),
+                                 c.nsisSetup.flags, outcmd, n]
+    echo(cmd)
+    if execShellCmd(cmd) == 0:
+      removeFile(n)
+    else:
+      quit("External program failed")
+
+proc xzDist(c: var ConfigData; windowsZip=false) =
+  let proj = toLowerAscii(c.name) & "-" & c.version
+  let tmpDir = if c.outdir.len == 0: "build" else: c.outdir
+
+  proc processFile(destFile, src: string) =
+    let dest = tmpDir / destFile
+    when false: echo "Copying ", src, " to ", dest
+    if not fileExists(src):
+      echo "[Warning] Source file doesn't exist: ", src
+    let destDir = dest.splitFile.dir
+    if not dirExists(destDir): createDir(destDir)
+    copyFileWithPermissions(src, dest)
+
+  if not windowsZip and not fileExists("build" / buildBatFile):
+    quit("No C sources found in ./build/, please build by running " &
+         "./koch csource -d:danger.")
+
+  if not windowsZip:
+    processFile(proj / buildBatFile, "build" / buildBatFile)
+    processFile(proj / buildBatFile32, "build" / buildBatFile32)
+    processFile(proj / buildBatFile64, "build" / buildBatFile64)
+    processFile(proj / buildShFile, "build" / buildShFile)
+    processFile(proj / makeFile, "build" / makeFile)
+    processFile(proj / installShFile, installShFile)
+    processFile(proj / deinstallShFile, deinstallShFile)
+    template processFileAux(src, dst) = processFile(dst, src)
+    gatherFiles(processFileAux, c.libpath, proj / "c_code")
+    for osA in 1..c.oses.len:
+      for cpuA in 1..c.cpus.len:
+        var dir = buildDir(osA, cpuA)
+        for k, f in walkDir("build" / dir):
+          if k == pcFile: processFile(proj / dir / extractFilename(f), f)
+  else:
+    for f in items(c.cat[fcWinBin]):
+      let filename = f.extractFilename
+      processFile(proj / "bin" / filename, f)
+
+  let osSpecific = if windowsZip: fcWindows else: fcUnix
+  for cat in items({fcConfig..fcOther, osSpecific, fcNimble}):
+    echo("Current category: ", cat)
+    for f in items(c.cat[cat]): processFile(proj / f, f)
+
+  # Copy the .nimble file over
+  let nimbleFile = c.nimblePkgName & ".nimble"
+  processFile(proj / nimbleFile, nimbleFile)
+
+  when true:
+    let oldDir = getCurrentDir()
+    setCurrentDir(tmpDir)
+    try:
+      if windowsZip:
+        if execShellCmd("7z a -tzip $1.zip $1" % proj) != 0:
+          echo("External program failed (zip)")
+        when false:
+          writeFile("config.txt", """;!@Install@!UTF-8!
+Title="Nim v$1"
+BeginPrompt="Do you want to configure Nim v$1?"
+RunProgram="tools\downloader.exe"
+;!@InstallEnd@!""" % NimVersion)
+          if execShellCmd("7z a -sfx7zS2.sfx -t7z $1.exe $1" % proj) != 0:
+            echo("External program failed (7z)")
+      else:
+        if execShellCmd("gtar cf $1.tar --exclude=.DS_Store $1" %
+                        proj) != 0:
+          # try old 'tar' without --exclude feature:
+          if execShellCmd("tar cf $1.tar $1" % proj) != 0:
+            echo("External program failed")
+
+        if execShellCmd("xz -9f $1.tar" % proj) != 0:
+          echo("External program failed")
+    finally:
+      setCurrentDir(oldDir)
+
+# -- prepare build files for .deb creation
+
+proc debDist(c: var ConfigData) =
+  if not fileExists(getOutputDir(c) / "build.sh"): quit("No build.sh found.")
+  if not fileExists(getOutputDir(c) / "install.sh"): quit("No install.sh found.")
+
+  if c.debOpts.shortDesc == "": quit("shortDesc must be set in the .ini file.")
+  if c.debOpts.licenses.len == 0:
+    echo("[Warning] No licenses specified for .deb creation.")
+
+  # -- Copy files into /tmp/..
+  echo("Copying source to tmp/niminst/deb/")
+  var currentSource = getCurrentDir()
+  var workingDir = getTempDir() / "niminst" / "deb"
+  var upstreamSource = (c.name.toLowerAscii() & "-" & c.version)
+
+  createDir(workingDir / upstreamSource)
+
+  template copyNimDist(f, dest: string) =
+    createDir((workingDir / upstreamSource / dest).splitFile.dir)
+    copyFile(currentSource / f, workingDir / upstreamSource / dest)
+
+  # Don't copy all files, only the ones specified in the config:
+  copyNimDist(buildShFile, buildShFile)
+  copyNimDist(makeFile, makeFile)
+  copyNimDist(installShFile, installShFile)
+  createDir(workingDir / upstreamSource / "build")
+  gatherFiles(copyNimDist, c.libpath, "build")
+  for osA in 1..c.oses.len:
+    for cpuA in 1..c.cpus.len:
+      var dir = buildDir(osA, cpuA)
+      for k, f in walkDir(dir):
+        if k == pcFile: copyNimDist(f, dir / extractFilename(f))
+  for cat in items({fcConfig..fcOther, fcUnix}):
+    for f in items(c.cat[cat]): copyNimDist(f, f)
+
+  # -- Create necessary build files for debhelper.
+
+  let mtnName = c.vars["mtnname"]
+  let mtnEmail = c.vars["mtnemail"]
+
+  prepDeb(c.name, c.version, mtnName, mtnEmail, c.debOpts.shortDesc,
+          c.description, c.debOpts.licenses, c.cat[fcUnixBin], c.cat[fcConfig],
+          c.cat[fcDoc], c.cat[fcLib], c.debOpts.buildDepends,
+          c.debOpts.pkgDepends)
+
+# ------------------- main ----------------------------------------------------
+
+proc main() =
+  var c: ConfigData
+  iniConfigData(c)
+  parseCmdLine(c)
+  parseIniFile(c)
+  if actionInno in c.actions:
+    setupDist(c)
+  if actionNsis in c.actions:
+    setupDist2(c)
+  if actionCSource in c.actions:
+    srcdist(c)
+  if actionScripts in c.actions:
+    writeInstallScripts(c)
+  if actionZip in c.actions:
+    xzDist(c, true)
+  if actionXz in c.actions:
+    xzDist(c)
+  if actionDeb in c.actions:
+    debDist(c)
+
+when isMainModule:
+  main()
diff --git a/tools/niminst/nsis.nimf b/tools/niminst/nsis.nimf
new file mode 100644
index 000000000..f4eb1d0cd
--- /dev/null
+++ b/tools/niminst/nsis.nimf
@@ -0,0 +1,445 @@
+#? stdtmpl(subsChar='?') | standard
+#proc generateNsisSetup(c: ConfigData): string =
+#  result = "; NSIS script generated by niminst\n" &
+#           "; To regenerate run ``niminst nsis`` or ``koch nsis``\n"
+
+;--------------------------------
+; Included headers
+  ; Modern User Interface 2.0 Header
+  !include "MUI2.nsh"
+
+  ; File Functions Header, used to get the current drive root.
+  !include "FileFunc.nsh"
+
+;--------------------------------
+; Global variables and defines
+  !define PRODUCT_NAME "?c.displayName"
+  !define PRODUCT_VERSION "?c.version"
+  !define PRODUCT_PUBLISHER "?c.authors"
+  !define PRODUCT_DIR_REGKEY "Software\Microsoft\Windows\CurrentVersion\App Paths\?{c.name}.exe"
+  !define PRODUCT_UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}"
+  !define PRODUCT_UNINST_ROOT_KEY "HKCU"
+  !define PRODUCT_STARTMENU_REGVAL "NSIS:StartMenuDir"
+
+
+;--------------------------------
+; General Setup Information
+
+  ; Name and output file
+  Name "?{c.name} ?{c.version}"
+  OutFile "?{c.name}_?{c.version}.exe"
+
+  ; Default installation folder
+  ; This is changed later (in .onInit) to the root directory, if possible.
+  InstallDir "$PROGRAMFILES?{when sizeof(int) == 8: "64" else: ""}\?{c.name}-?{c.version}\"
+
+  ; Get installation folder from registry if available
+  InstallDirRegKey HKCU "Software\c.name\c.version" ""
+
+  ; Request user level application privileges.
+  RequestExecutionLevel user
+
+  ; Allow installation to the root drive directory.
+  AllowRootDirInstall false
+
+  ; Maximum compression!
+  SetCompressor /SOLID /FINAL lzma
+
+  ; Installer and Uninstaller Icons
+  ; Icon "nim.ico"
+  ; UninstallIcon "nim.ico"
+
+  ; Set installation details to be shown by default
+  ShowInstDetails show
+  ShowUnInstDetails show
+
+;--------------------------------
+; Interface Settings
+
+  ; Warn the user if aborting during installation/uninstallation
+  !define MUI_ABORTWARNING
+  !define MUI_UNABORTWARNING
+
+  ; Don't show a description for sections
+  !define MUI_COMPONENTSPAGE_NODESC
+
+;--------------------------------
+; Pages
+
+  ; Setup the installer pages
+  !insertmacro MUI_PAGE_WELCOME
+  !insertmacro MUI_PAGE_LICENSE "?{expandFilename(c.license)}"
+  !insertmacro MUI_PAGE_COMPONENTS
+  !insertmacro MUI_PAGE_DIRECTORY
+
+  ; Setup the start menu entry page
+  var ICONS_GROUP
+  !define MUI_STARTMENUPAGE_DEFAULTFOLDER "?{c.displayName}"
+  !define MUI_STARTMENUPAGE_REGISTRY_ROOT "${PRODUCT_UNINST_ROOT_KEY}"
+  !define MUI_STARTMENUPAGE_REGISTRY_KEY "${PRODUCT_UNINST_KEY}"
+  !define MUI_STARTMENUPAGE_REGISTRY_VALUENAME "${PRODUCT_STARTMENU_REGVAL}"
+  !insertmacro MUI_PAGE_STARTMENU Application $ICONS_GROUP
+
+  !insertmacro MUI_PAGE_INSTFILES
+  !insertmacro MUI_PAGE_FINISH
+
+  ; Setup the uninstaller pages
+  !insertmacro MUI_UNPAGE_CONFIRM
+  !insertmacro MUI_UNPAGE_INSTFILES
+
+;--------------------------------
+;Languages
+
+  !insertmacro MUI_LANGUAGE "English"
+
+;--------------------------------
+;Installer Sections
+
+  ; The core section. This is comprised of a base Nim installation,
+  ; such as what would be retrieved via git, and an already bootstrapped
+  ; Nim binary.
+  Section "Core Files" CoreSection
+    ; This is a mandotory section
+    SectionIn RO
+
+    ; Output files to the base installation directory
+    SetOutPath "$INSTDIR"
+
+    ; Only overwrite newer files
+    SetOverwrite ifnewer
+
+    ; Write all the files to the output directory.
+    #for i in low(FileCategory)..fcWindows:
+    #  for f in items(c.cat[i]):
+         SetOutPath "$INSTDIR\?{splitFile(f).dir.toWin}"
+         File "?{expandFilename(f).toWin}"
+    #  end for
+    #end for
+
+    ; Write out the uninstaller
+    WriteUninstaller "$INSTDIR\uninstaller.exe"
+  SectionEnd
+
+  Section "-Add Registry Keys" RegistrySection
+    ; Write application registry keys
+    WriteRegStr HKCU "${PRODUCT_DIR_REGKEY}" "" "$INSTDIR\bin\?{c.name}.exe"
+    WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "DisplayName" "$(^Name)"
+    WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "UninstallString" "$INSTDIR\uninstaller.exe"
+    WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "DisplayIcon" "$INSTDIR\bin\?{c.name}.exe"
+    WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "DisplayVersion" "${PRODUCT_VERSION}"
+    WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "Publisher" "${PRODUCT_PUBLISHER}"
+    ; Reset the output path
+    SetOutPath "$INSTDIR"
+  SectionEnd
+
+  ; Section for adding the shortcuts related to files and applications
+  Section "-Setup Shortcuts" ShortcutsSection
+    !insertmacro MUI_STARTMENU_WRITE_BEGIN Application
+      CreateDirectory "$SMPROGRAMS\$ICONS_GROUP"
+
+      #if c.app == appConsole:
+        CreateShortCut "$SMPROGRAMS\$ICONS_GROUP\?{c.displayName}.lnk" "$INSTDIR\start.bat"
+        CreateShortCut "$DESKTOP\?{c.displayName}.lnk" "$INSTDIR\start.bat"
+      #else:
+        CreateShortCut "$SMPROGRAMS\$ICONS_GROUP\?{c.displayName}.lnk" "$INSTDIR\?{c.name}.exe"
+        CreateShortCut "$DESKTOP\?{c.displayName}.lnk" "$INSTDIR\?{c.name}.exe"
+      #end if
+
+      ; Write the shortcut to the uninstaller
+      CreateShortCut "$SMPROGRAMS\$ICONS_GROUP\Uninstall.lnk" "$INSTDIR\uninstaller.exe"
+    !insertmacro MUI_STARTMENU_WRITE_END
+  SectionEnd
+
+  ; Section for adding tools to the PATH variable
+  Section "Setup Path Environment" PathSection
+    Push "$INSTDIR\bin"
+    Call AddToPath
+    Push "$INSTDIR\dist\mingw"
+    Call AddToPath
+    Push "$INSTDIR\dist\mingw\bin"
+    Call AddToPath
+    Push "$PROFILE\.nimble\bin"
+    Call AddToPath
+  SectionEnd
+
+  ; The downloadable sections. These sections are automatically generated by
+  ; niminst and the template filters.
+  #var i = 0
+  #for download in c.downloads:
+  #  inc i
+  #  let d = download.split('|')
+  #  if d.len != 5 and d.len != 6:
+  #    quit("download string needs 5..6 parts: " & download)
+  #  end if
+  #  let sectionName = d[0]
+  #  let dir = d[1]
+  #  let zipName = d[2]
+  #  let size = d[3]
+  #  let url = d[4]
+  Section /o "?sectionName" ?{i}Section
+    ; Add the section size to the total size.
+    AddSize ?size
+
+    ; Download the file, and if successful, extract it to the given directory
+    ; otherwise,
+    retry:
+    NSISdl::download "?url" "$TEMP\?zipName"
+    Pop $0
+    ${If} $0 == "success"
+      ZipDLL::extractall "$TEMP\?zipName" "$INSTDIR\?dir"
+      Delete "$TEMP\?zipName"
+    ${ElseIf} $0 == "cancel"
+      MessageBox MB_ICONQUESTION|MB_YESNO|MB_TOPMOST \
+                 "Download of component '?sectionName' cancelled. Continue installation process??" \
+                 IDYES ignore
+      abort
+    ${Else}
+      MessageBox MB_ICONSTOP|MB_ABORTRETRYIGNORE|MB_TOPMOST "Error: $0" \
+                 IDRETRY retry IDIGNORE ignore
+      abort
+    ${EndIf}
+
+    ; Shortcuts
+    #  if d.len >= 6:
+    #    let startMenuEntry = d[5]
+    #    let e = splitFile(startMenuEntry).name.capitalizeAscii
+      !insertmacro MUI_STARTMENU_WRITE_BEGIN Application
+        CreateShortCut "$SMPROGRAMS\$ICONS_GROUP\?{e}.lnk" "$INSTDIR\?dir\?{startMenuEntry.toWin}"
+      !insertmacro MUI_STARTMENU_WRITE_END
+    #  end if
+
+    ignore:
+  SectionEnd
+  #end
+
+;--------------------------------
+; Section Descriptions
+  ; Series of strings describing each section
+  ; LangString DESC_CoreSection ${LANG_ENGLISH} "Core Nim files"
+
+  ; The macros to actually insert the descriptions into the sections.
+  ; Each description above should have a corresponding MUI_DESCRIPTION_TEXT
+  ; macro linking the section to the description.
+  ; !insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN
+  ;   !insertmacro MUI_DESCRIPTION_TEXT ${CoreSection} $(DESC_CoreSection)
+  ; !insertmacro MUI_FUNCTION_DESCRIPTION_END
+
+
+;--------------------------------
+; Uninstaller Sections
+
+  Section "Uninstall"
+    ; Remove previously created shortcuts
+    !insertmacro MUI_STARTMENU_GETFOLDER "Application" $ICONS_GROUP
+    Delete "$DESKTOP\?{c.displayName}.lnk"
+
+    ; Remove installed application files
+    RMDir /r "$SMPROGRAMS\$ICONS_GROUP"
+    RMDir /r "$INSTDIR"
+
+    ; Remove the previously created registry key
+    DeleteRegKey ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}"
+    DeleteRegKey HKCU "${PRODUCT_DIR_REGKEY}"
+    SetAutoClose true
+
+    ; Remove entries from the PATH environment variable
+    Push "$INSTDIR\bin"
+    Call un.RemoveFromPath
+    Push "$INSTDIR\dist\mingw"
+    Call un.RemoveFromPath
+    Push "$INSTDIR\dist\mingw\bin"
+    Call un.RemoveFromPath
+    Push "$PROFILE\.nimble\bin"
+    Call un.RemoveFromPath
+  SectionEnd
+
+;--------------------------------
+; Function hooks
+
+  Function .onInit
+    ${GetRoot} "$EXEDIR" $R0
+    strCpy $INSTDIR "$R0\?{c.name}"
+  FunctionEnd
+
+
+;--------------------------------------------------------------------
+; Path functions
+;
+; Based on example from:
+; http://nsis.sourceforge.net/Path_Manipulation
+;
+; Actually based on:
+; https://www.smartmontools.org/browser/trunk/smartmontools/os_win32/installer.nsi#L636
+
+
+!include "WinMessages.nsh"
+
+; Registry Entry for environment (NT4,2000,XP)
+; All users:
+;!define Environ 'HKLM "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"'
+; Current user only:
+!define Environ 'HKCU "Environment"'
+
+
+; AddToPath - Appends dir to PATH
+;   (does not work on Win9x/ME)
+;
+; Usage:
+;   Push "dir"
+;   Call AddToPath
+
+Function AddToPath
+  Exch $0
+  Push $1
+  Push $2
+  Push $3
+  Push $4
+
+  ; NSIS ReadRegStr returns empty string on string overflow
+  ; Native calls are used here to check actual length of PATH
+
+  ; $4 = RegOpenKey(HKEY_CURRENT_USER, "Environment", &$3)
+  System::Call "advapi32::RegOpenKey(i 0x80000001, t'Environment', *i.r3) i.r4"
+  IntCmp $4 0 0 done done
+  ; $4 = RegQueryValueEx($3, "PATH", (DWORD*)0, (DWORD*)0, &$1, ($2=NSIS_MAX_STRLEN, &$2))
+  ; RegCloseKey($3)
+  System::Call "advapi32::RegQueryValueEx(i $3, t'PATH', i 0, i 0, t.r1, *i ${NSIS_MAX_STRLEN} r2) i.r4"
+  System::Call "advapi32::RegCloseKey(i $3)"
+
+  IntCmp $4 234 0 +4 +4 ; $4 == ERROR_MORE_DATA
+    DetailPrint "AddToPath: original length $2 > ${NSIS_MAX_STRLEN}"
+    MessageBox MB_OK "PATH not updated, original length $2 > ${NSIS_MAX_STRLEN}"
+    Goto done
+
+  IntCmp $4 0 +5 ; $4 != NO_ERROR
+    IntCmp $4 2 +3 ; $4 != ERROR_FILE_NOT_FOUND
+      DetailPrint "AddToPath: unexpected error code $4"
+      Goto done
+    StrCpy $1 ""
+
+  ; Check if already in PATH
+  Push "$1;"
+  Push "$0;"
+  Call StrStr
+  Pop $2
+  StrCmp $2 "" 0 done
+  Push "$1;"
+  Push "$0\;"
+  Call StrStr
+  Pop $2
+  StrCmp $2 "" 0 done
+
+  ; Prevent NSIS string overflow
+  StrLen $2 $0
+  StrLen $3 $1
+  IntOp $2 $2 + $3
+  IntOp $2 $2 + 2 ; $2 = strlen(dir) + strlen(PATH) + sizeof(";")
+  IntCmp $2 ${NSIS_MAX_STRLEN} +4 +4 0
+    DetailPrint "AddToPath: new length $2 > ${NSIS_MAX_STRLEN}"
+    MessageBox MB_OK "PATH not updated, new length $2 > ${NSIS_MAX_STRLEN}."
+    Goto done
+
+  ; Append dir to PATH
+  DetailPrint "Add to PATH: $0"
+  StrCpy $2 $1 1 -1
+  StrCmp $2 ";" 0 +2
+    StrCpy $1 $1 -1 ; remove trailing ';'
+  StrCmp $1 "" +2   ; no leading ';'
+    StrCpy $0 "$1;$0"
+  WriteRegExpandStr ${Environ} "PATH" $0
+  SendMessage ${HWND_BROADCAST} ${WM_WININICHANGE} 0 "STR:Environment" /TIMEOUT=5000
+
+done:
+  Pop $4
+  Pop $3
+  Pop $2
+  Pop $1
+  Pop $0
+FunctionEnd
+
+
+; RemoveFromPath - Removes dir from PATH
+;
+; Usage:
+;   Push "dir"
+;   Call RemoveFromPath
+
+Function un.RemoveFromPath
+  Exch $0
+  Push $1
+  Push $2
+  Push $3
+  Push $4
+  Push $5
+  Push $6
+
+  ReadRegStr $1 ${Environ} "PATH"
+  StrCpy $5 $1 1 -1
+  StrCmp $5 ";" +2
+    StrCpy $1 "$1;" ; ensure trailing ';'
+  Push $1
+  Push "$0;"
+  Call un.StrStr
+  Pop $2 ; pos of our dir
+  StrCmp $2 "" done
+
+  DetailPrint "Remove from PATH: $0"
+  StrLen $3 "$0;"
+  StrLen $4 $2
+  StrCpy $5 $1 -$4 ; $5 is now the part before the path to remove
+  StrCpy $6 $2 "" $3 ; $6 is now the part after the path to remove
+  StrCpy $3 "$5$6"
+  StrCpy $5 $3 1 -1
+  StrCmp $5 ";" 0 +2
+    StrCpy $3 $3 -1 ; remove trailing ';'
+  WriteRegExpandStr ${Environ} "PATH" $3
+  SendMessage ${HWND_BROADCAST} ${WM_WININICHANGE} 0 "STR:Environment" /TIMEOUT=5000
+
+done:
+  Pop $6
+  Pop $5
+  Pop $4
+  Pop $3
+  Pop $2
+  Pop $1
+  Pop $0
+FunctionEnd
+
+
+; StrStr - find substring in a string
+;
+; Usage:
+;   Push "this is some string"
+;   Push "some"
+;   Call StrStr
+;   Pop $0 ; "some string"
+
+!macro StrStr un
+Function ${un}StrStr
+  Exch $R1 ; $R1=substring, stack=[old$R1,string,...]
+  Exch     ;                stack=[string,old$R1,...]
+  Exch $R2 ; $R2=string,    stack=[old$R2,old$R1,...]
+  Push $R3
+  Push $R4
+  Push $R5
+  StrLen $R3 $R1
+  StrCpy $R4 0
+  ; $R1=substring, $R2=string, $R3=strlen(substring)
+  ; $R4=count, $R5=tmp
+  loop:
+    StrCpy $R5 $R2 $R3 $R4
+    StrCmp $R5 $R1 done
+    StrCmp $R5 "" done
+    IntOp $R4 $R4 + 1
+    Goto loop
+done:
+  StrCpy $R1 $R2 "" $R4
+  Pop $R5
+  Pop $R4
+  Pop $R3
+  Pop $R2
+  Exch $R1 ; $R1=old$R1, stack=[result,...]
+FunctionEnd
+!macroend
+!insertmacro StrStr ""
+!insertmacro StrStr "un."
diff --git a/tools/niminst/setup.ico b/tools/niminst/setup.ico
new file mode 100644
index 000000000..867163046
--- /dev/null
+++ b/tools/niminst/setup.ico
Binary files differdiff --git a/tools/niminst/uninstall.ico b/tools/niminst/uninstall.ico
new file mode 100644
index 000000000..aff054644
--- /dev/null
+++ b/tools/niminst/uninstall.ico
Binary files differ