summary refs log blame commit diff stats
path: root/copying.txt
blob: a7ba3143f2027559d79f18722d91120bd6c0eb5e (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
                                                     
                                                
 
                                                           
 





                                                                             
 

                                                                          
 







                                                                             
                                                                   
=====================================================
Nim -- a Compiler for Nim. https://nim-lang.org/

Copyright (C) 2006-2020 Andreas Rumpf. All rights reserved.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

[ MIT license: http://www.opensource.org/licenses/mit-license.php ]
d2200; background-color: #fff0f0 } /* Literal.String.Affix */ .highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */ .highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */ .highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */ .highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */ .highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */ .highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */ .highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */ .highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */ .highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */ .highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */ .highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */ .highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */ .highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */ .highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */ .highlight .vc { color: #336699 } /* Name.Variable.Class */ .highlight .vg { color: #dd7700 } /* Name.Variable.Global */ .highlight .vi { color: #3333bb } /* Name.Variable.Instance */ .highlight .vm { color: #336699 } /* Name.Variable.Magic */ .highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
# -------------- post unzip steps ---------------------------------------------

import strutils, os, osproc, streams, browsers

const
  arch = $(sizeof(int)*8)
  mingw = "mingw$1.7z" % arch
  url = r"https://nim-lang.org/download/" & mingw

var
  interactive = true

type
  DownloadResult = enum
    Failure,
    Manual,
    Success

proc expand(s: string): string =
  try:
    result = expandFilename(s)
  except OSError, IOError:
    result = s

proc unzip(): bool =
  if not fileExists("dist" / mingw):
    echo "Could not find ", "dist" / mingw
    return false
  try:
    let p = osproc.startProcess(r"bin\7zG.exe", getCurrentDir() / r"dist",
                                ["x", mingw])
    if p.waitForExit != 0:
      echo "Unpacking failed: " & mingw
    else:
      result = true
  except:
    result = false

proc downloadMingw(): DownloadResult =
  let curl = findExe"curl"
  var cmd: string
  if curl.len > 0:
    cmd = quoteShell(curl) & " --out " & "dist" / mingw & " " & url
  elif fileExists"bin/nimgrab.exe":
    cmd = r"bin\nimgrab.exe " & url & " dist" / mingw
  if cmd.len > 0:
    if execShellCmd(cmd) != 0:
      echo "download failed! ", cmd
      if interactive:
        openDefaultBrowser(url)
        result = Manual
      else:
        result = Failure
    else:
      if unzip(): result = Success
  else:
    if interactive:
      openDefaultBrowser(url)
      result = Manual
    else:
      result = Failure

when defined(windows):
  import registry

  proc askBool(m: string): bool =
    stdout.write m
    if not interactive:
      stdout.writeLine "y (non-interactive mode)"
      return true
    while true:
      try:
        let answer = stdin.readLine().normalize
        case answer
        of "y", "yes":
          return true
        of "n", "no":
          return false
        else:
          echo "Please type 'y' or 'n'"
      except EOFError:
        quit(1)

  proc askNumber(m: string; a, b: int): int =
    stdout.write m
    stdout.write " [" & $a & ".." & $b & "] "
    if not interactive:
      stdout.writeLine $a & " (non-interactive mode)"
      return a
    while true:
      let answer = stdin.readLine()
      try:
        result = parseInt answer
        if result < a or result > b:
          raise newException(ValueError, "number out of range")
        break
      except ValueError:
        echo "Please type in a number between ", a, " and ", b

  proc patchConfig(mingw: string) =
    const
      cfgFile = "config/nim.cfg"
      lookFor = """#gcc.path = r"$nim\dist\mingw\bin""""
      replacePattern = """gcc.path = r"$1""""
    try:
      let cfg = readFile(cfgFile)
      let newCfg = cfg.replace(lookFor, replacePattern % mingw)
      if newCfg == cfg:
        echo "Could not patch 'config/nim.cfg' [Error]"
        echo "Reason: patch substring not found:"
        echo lookFor
      else:
        writeFile(cfgFile, newCfg)
    except IOError:
      echo "Could not access 'config/nim.cfg' [Error]"

  proc tryGetUnicodeValue(path, key: string; handle: HKEY): string =
    try:
      result = getUnicodeValue(path, key, handle)
    except:
      result = ""

  proc addToPathEnv*(e: string) =
    var p = tryGetUnicodeValue(r"Environment", "Path", HKEY_CURRENT_USER)
    if p.len > 0:
      p.add ";"
      p.add e
    else:
      p = e
    setUnicodeValue(r"Environment", "Path", p, HKEY_CURRENT_USER)

  proc createShortcut(src, dest: string; icon = "") =
    var cmd = "bin\\makelink.exe \"" & src & "\" \"\" \"" & dest &
         ".lnk\" \"\" 1 \"" & splitFile(src).dir & "\""
    if icon.len != 0:
      cmd.add " \"" & icon & "\" 0"
    discard execShellCmd(cmd)

  proc createStartMenuEntry*(override = false) =
    let appdata = getEnv("APPDATA")
    if appdata.len == 0: return
    let dest = appdata & r"\Microsoft\Windows\Start Menu\Programs\Nim-" &
               NimVersion
    if dirExists(dest): return
    if override or askBool("Would like to add Nim-" & NimVersion &
               " to your start menu? (y/n) "):
      createDir(dest)
      createShortcut(getCurrentDir() / "tools" / "start.bat", dest / "Nim",
                     getCurrentDir() / r"icons\nim.ico")
      if fileExists("doc/overview.html"):
        createShortcut(getCurrentDir() / "doc" / "html" / "overview.html",
                       dest / "Overview")
      if dirExists(r"dist\aporia-0.4.0"):
        createShortcut(getCurrentDir() / r"dist\aporia-0.4.0\bin\aporia.exe",
                       dest / "Aporia")

  proc checkGccArch(mingw: string): bool =
    let gccExe = mingw / r"gcc.exe"
    if fileExists(gccExe):
      const nimCompat = "nim_compat.c"
      writeFile(nimCompat, """typedef int
        Nim_and_C_compiler_disagree_on_target_architecture[
          $# == sizeof(void*) ? 1 : -1];
      """ % $sizeof(int))
      try:
        let p = startProcess(gccExe, "", ["-c", nimCompat], nil,
                            {poStdErrToStdOut, poUsePath})
        #echo p.outputStream.readAll()
        result = p.waitForExit() == 0
      except OSError, IOError:
        result = false
      finally:
        removeFile(nimCompat)
        removeFile(nimCompat.changeFileExt("o"))

  proc defaultMingwLocations(): seq[string] =
    proc probeDir(dir: string; result: var seq[string]) =
      for k, x in walkDir(dir, relative=true):
        if k in {pcDir, pcLinkToDir}:
          if x.contains("mingw") or x.contains("posix"):
            let dest = dir / x
            probeDir(dest, result)
            result.add(dest)

    result = @["dist/mingw", "../mingw", r"C:\mingw"]
    let pfx86 = getEnv("programfiles(x86)")
    let pf = getEnv("programfiles")
    when hostCPU == "i386":
      probeDir(pfx86, result)
      probeDir(pf, result)
    else:
      probeDir(pf, result)
      probeDir(pfx86, result)

  proc tryDirs(incompat: var seq[string]; dirs: varargs[string]): string =
    let bits = $(sizeof(pointer)*8)
    for d in dirs:
      if dirExists d:
        let x = expand(d / "bin")
        if x.len != 0:
          if checkGccArch(x): return x
          else: incompat.add x
      elif dirExists(d & bits):
        let x = expand((d & bits) / "bin")
        if x.len != 0:
          if checkGccArch(x): return x
          else: incompat.add x

proc main() =
  when defined(windows):
    let nimDesiredPath = expand(getCurrentDir() / "bin")
    let nimbleBin = getEnv("USERPROFILE") / ".nimble" / "bin"
    let nimbleDesiredPath = expand(nimbleBin)
    let p = tryGetUnicodeValue(r"Environment", "Path",
      HKEY_CURRENT_USER) & ";" & tryGetUnicodeValue(
        r"System\CurrentControlSet\Control\Session Manager\Environment", "Path",
        HKEY_LOCAL_MACHINE)
    var nimAlreadyInPath = false
    var nimbleAlreadyInPath = false
    var mingWchoices: seq[string] = @[]
    var incompat: seq[string] = @[]
    for x in p.split(';'):
      if x.len == 0: continue
      let y = try: expandFilename(if x[0] == '"' and x[^1] == '"':
                                    substr(x, 1, x.len-2) else: x)
              except OSError as e:
                if e.errorCode == 0: x else: ""
              except: ""
      if y.cmpIgnoreCase(nimDesiredPath) == 0:
        nimAlreadyInPath = true
      elif y.cmpIgnoreCase(nimbleDesiredPath) == 0:
        nimbleAlreadyInPath = true
      elif y.toLowerAscii.contains("mingw"):
        if dirExists(y):
          if checkGccArch(y): mingWchoices.add y
          else: incompat.add y

    if nimAlreadyInPath:
      echo "bin\\nim.exe is already in your PATH [Skipping]"
    else:
      if askBool("nim.exe is not in your PATH environment variable.\n" &
          "Should it be added permanently? (y/n) "):
        addToPathEnv(nimDesiredPath)

    if nimbleAlreadyInPath:
      echo nimbleDesiredPath & " is already in your PATH [Skipping]"
    else:
      if askBool(nimbleDesiredPath & " is not in your PATH environment variable.\n" &
          "Should it be added permanently? (y/n) "):
        addToPathEnv(nimbleDesiredPath)

    if mingWchoices.len == 0:
      # No mingw in path, so try a few locations:
      let alternative = tryDirs(incompat, defaultMingwLocations())
      if alternative.len == 0:
        if incompat.len > 0:
          echo "The following *incompatible* MingW installations exist"
          for x in incompat: echo x
          echo "*incompatible* means Nim and GCC disagree on the size of a pointer."
        echo "No compatible MingW candidates found " &
             "in the standard locations [Error]"
        if askBool("Do you want to download MingW from Nim's website? (y/n) "):
          let dest = getCurrentDir() / "dist"
          var retry = false
          case downloadMingw()
          of Manual:
            echo "After download, move it to: ", dest
            if askBool("Download successful? (y/n) "):
              while not fileExists("dist" / mingw):
                echo "could not find: ", "dist" / mingw
                if not askBool("Try again? (y/n) "): break
              if unzip(): retry = true
          of Failure: discard
          of Success:
            retry = true
          if retry:
            incompat.setLen 0
            let alternative = tryDirs(incompat, defaultMingwLocations())
            if alternative.len == 0:
              if incompat.len > 0:
                echo "The following *incompatible* MingW installations exist"
                for x in incompat: echo x
                echo "*incompatible* means Nim and GCC disagree on the size of a pointer."
              echo "Still no compatible MingW candidates found " &
                   "in the standard locations [Error]"
            else:
              echo "Patching Nim's config to use:"
              echo alternative
              patchConfig(alternative)
      else:
        if askBool("Found a MingW directory that is not in your PATH.\n" &
                   alternative &
                   "\nShould it be added to your PATH permanently? (y/n) "):
          addToPathEnv(alternative)
        elif askBool("Do you want to patch Nim's config to use this? (y/n) "):
          patchConfig(alternative)
    elif mingWchoices.len == 1:
      if askBool("MingW installation found at " & mingWchoices[0] & "\n" &
         "Do you want to patch Nim's config to use this?\n" &
         "(Not required since it's in your PATH!) (y/n) "):
        patchConfig(mingWchoices[0])
    else:
      echo "Multiple MingW installations found: "
      for i in 0..high(mingWchoices):
        echo "[", i, "] ", mingWchoices[i]
      if askBool("Do you want to patch Nim's config to use one of these? (y/n) "):
        let idx = askNumber("Which one do you want to use for Nim? ",
            1, len(mingWchoices))
        patchConfig(mingWchoices[idx-1])
    createStartMenuEntry()
  else:
    echo("Add ", getCurrentDir(), "/bin to your PATH...")

when isMainModule:
  when defined(testdownload):
    discard downloadMingw()
  else:
    if "-y" in commandLineParams():
      interactive = false
    main()