getnim/src/getnimpkg/switcher.nim

245 lines
8.8 KiB
Nim

import os, strutils, osproc, pegs
import nimblepkg/[cli, version, options]
from nimblepkg/packageinfo import getNameVersion
import cliparams, common
when defined(windows):
import env
proc compileProxyexe() =
var cmd =
when defined(windows):
"cmd /C \"cd ../../ && nimble c"
else:
"cd ../../ && nimble c"
when defined(release):
cmd.add " -d:release"
when defined(staticBuild):
cmd.add " -d:staticBuild"
cmd.add " src/getnimpkg/proxyexe"
when defined(windows):
cmd.add("\"")
let (output, exitCode) = gorgeEx(cmd)
doAssert exitCode == 0, $(output, cmd)
static: compileProxyexe()
const
proxyExe = staticRead("proxyexe".addFileExt(ExeExt))
proc getInstallationDir*(params: CliParams, version: Version): string =
return params.getInstallDir() / ("nim-$1" % $version)
proc isVersionInstalled*(params: CliParams, version: Version): bool =
return fileExists(params.getInstallationDir(version) / "bin" /
"nim".addFileExt(ExeExt))
proc getSelectedPath*(params: CliParams): string =
if fileExists(params.getCurrentFile()): readFile(params.getCurrentFile())
else: ""
proc getProxyPath(params: CliParams, bin: string): string =
return params.getBinDir() / bin.addFileExt(ExeExt)
proc areProxiesInstalled(params: CliParams, proxies: openarray[string]): bool =
result = true
for proxy in proxies:
# Verify that proxy exists.
let path = params.getProxyPath(proxy)
if not fileExists(path):
return false
# Verify that proxy binary is up-to-date.
let contents = readFile(path)
if contents != proxyExe:
return false
proc isDefaultCCInPath*(params: CliParams): bool =
# Fixes issue #104
when defined(macosx):
return findExe("clang") != ""
else:
return findExe("gcc") != ""
proc needsCCInstall*(params: CliParams): bool =
## Determines whether the system needs a C compiler to be installed.
let inPath = isDefaultCCInPath(params)
when defined(windows):
let inMingwDir =
when defined(windows):
fileExists(params.getMingwBin() / "gcc".addFileExt(ExeExt))
else: false
# Check whether the `gcc` we have in PATH is actually getnim's proxy exe.
# If so and toolchain mingw dir doesn't exit then we need to install.
if inPath and findExe("gcc") == params.getProxyPath("gcc"):
return not inMingwDir
return not inPath
proc needsDLLInstall*(params: CliParams): bool =
## Determines whether DLLs need to be installed (Windows-only).
##
## TODO: In the future we can probably extend this and let the user
## know what DLLs they are missing on all operating systems.
proc isInstalled(params: CliParams, name: string): bool =
let
inPath = findExe(name, extensions=["dll"]) != ""
inNimbleBin = fileExists(params.getBinDir() / name & ".dll")
return inPath or inNimbleBin
for dll in ["libeay", "pcre", "pdcurses", "sqlite3_", "ssleay"]:
for bit in ["32", "64"]:
result = not isInstalled(params, dll & bit)
if result: return
proc getNimbleVersion(toolchainPath: string): Version =
result = newVersion("0.8.6") # We assume that everything is fine.
let command = toolchainPath / "bin" / "nimble".addFileExt(ExeExt)
let (output, _) = execCmdEx(command & " -v")
var matches: array[0 .. MaxSubpatterns, string]
if output.find(peg"'nimble v'{(\d+\.)+\d+}", matches) != -1:
result = newVersion(matches[0])
else:
display("Warning:", "Could not find toolchain's Nimble version.",
Warning, MediumPriority)
proc writeProxy(bin: string, params: CliParams) =
# Create the ~/.nimble/bin dir in case it doesn't exist.
createDir(params.getBinDir())
let proxyPath = params.getProxyPath(bin)
if bin == "nimble":
# Check for "lib" dir in ~/.nimble. Issue #13.
let dir = params.nimbleOptions.getNimbleDir() / "lib"
if dirExists(dir):
let msg = ("Nimble will fail because '$1' exists. Would you like me " &
"to remove it?") % dir
if prompt(dontForcePrompt, msg):
removeDir(dir)
display("Removed", dir, priority = HighPriority)
if symlinkExists(proxyPath):
let msg = "Symlink for '$1' detected in '$2'. Can I remove it?" %
[bin, proxyPath.splitFile().dir]
if not prompt(dontForcePrompt, msg): return
let symlinkPath = expandSymlink(proxyPath)
removeFile(proxyPath)
display("Removed", "symlink pointing to $1" % symlinkPath,
priority = HighPriority)
# Don't write the file again if it already exists.
if fileExists(proxyPath) and readFile(proxyPath) == proxyExe: return
try:
writeFile(proxyPath, proxyExe)
except IOError:
display("Warning:", "component '$1' possibly in use, write failed" % bin, Warning,
priority = HighPriority)
return
# Make sure the exe has +x flag.
setFilePermissions(proxyPath,
getFilePermissions(proxyPath) + {fpUserExec})
display("Installed", "component '$1'" % bin, priority = HighPriority)
# Check whether this is in the user's PATH.
let fromPATH = findExe(bin)
display("Debug:", "Proxy path: " & proxyPath, priority = DebugPriority)
display("Debug:", "findExe: " & fromPATH, priority = DebugPriority)
if fromPATH == "" and not params.firstInstall:
let msg =
when defined(windows):
"Binary '$1' isn't in your PATH" % bin
else:
"Binary '$1' isn't in your PATH. Ensure that '$2' is in your PATH." %
[bin, params.getBinDir()]
display("Hint:", msg, Warning, HighPriority)
elif fromPATH != "" and fromPATH != proxyPath:
display("Warning:", "Binary '$1' is shadowed by '$2'." %
[bin, fromPATH], Warning, HighPriority)
display("Hint:", "Ensure that '$1' is before '$2' in the PATH env var." %
[params.getBinDir(), fromPATH.splitFile.dir], Warning, HighPriority)
proc switchToPath(filepath: string, params: CliParams): bool =
## Switches to the specified file path that should point to the root of
## the Nim repo.
##
## Returns `false` when no switching occurs (because that version was
## already selected).
result = true
if not fileExists(filepath / "bin" / "nim".addFileExt(ExeExt)):
let msg = "No 'nim' binary found in '$1'." % filepath / "bin"
raise newException(GetnimError, msg)
# Check Nimble version to give a warning when it's too old.
let nimbleVersion = getNimbleVersion(filepath)
if nimbleVersion < newVersion("0.8.6"):
display("Warning:", ("Nimble v$1 is not supported by getnim, using it " &
"will yield errors.") % $nimbleVersion,
Warning, HighPriority)
display("Hint:", "Installing Nim from GitHub will ensure that a working " &
"version of Nimble is installed. You can do so by " &
"running `getnim \"#v0.16.0\"` or similar.",
Warning, HighPriority)
var proxiesToInstall = @proxies
# Handle MingW proxies.
when defined(windows):
if not isDefaultCCInPath(params):
let mingwBin = getMingwBin(params)
if not fileExists(mingwBin / "gcc".addFileExt(ExeExt)):
let msg = "No 'gcc' binary found in '$1'." % mingwBin
raise newException(GetnimError, msg)
proxiesToInstall.add(mingwProxies)
# Return early if this version is already selected.
let selectedPath = params.getSelectedPath()
let proxiesInstalled = params.areProxiesInstalled(proxiesToInstall)
if selectedPath == filepath and proxiesInstalled:
return false
else:
# Write selected path to "current file".
writeFile(params.getCurrentFile(), filepath)
# Create the proxy executables.
for proxy in proxiesToInstall:
writeProxy(proxy, params)
when defined(windows):
if not isNimbleBinInPath(params):
display("Hint:", "Use 'getnim <version/channel> --firstInstall' to add\n" &
"$1 to your PATH." % params.getBinDir(), Warning, HighPriority)
proc switchTo*(version: Version, params: CliParams) =
## Switches to the specified version by writing the appropriate proxy
## into $nimbleDir/bin.
assert params.isVersionInstalled(version),
"Cannot switch to non-installed version"
if switchToPath(params.getInstallationDir(version), params):
display("Switched", "to Nim " & $version, Success, HighPriority)
else:
display("Info:", "Version $1 already selected" % $version,
priority = HighPriority)
proc switchTo*(filepath: string, params: CliParams) =
## Switches to an existing Nim installation.
let filepath = expandFilename(filepath)
if switchToPath(filepath, params):
display("Switched", "to Nim ($1)" % filepath, Success, HighPriority)
else:
display("Info:", "Path '$1' already selected" % filepath,
priority = HighPriority)
proc getSelectedVersion*(params: CliParams): Version =
let path = getSelectedPath(params)
let (_, version) = getNameVersion(path)
return version.newVersion