354 lines
12 KiB
Nim
354 lines
12 KiB
Nim
# Copyright (C) Dominik Picheta. All rights reserved.
|
|
# BSD-3-Clause License. Look at license.txt for more info.
|
|
import os, strutils, algorithm
|
|
|
|
import nimblepkg/[cli, version]
|
|
import nimblepkg/common as nimbleCommon
|
|
from nimblepkg/packageinfo import getNameVersion
|
|
|
|
import getnimpkg/[download, builder, switcher, common, cliparams, versions]
|
|
import getnimpkg/[utils, channel]
|
|
|
|
when defined(windows):
|
|
import getnimpkg/env
|
|
|
|
import times
|
|
|
|
proc installVersion(version: Version, params: CliParams) =
|
|
let
|
|
extractDir = params.getInstallationDir(version)
|
|
updated = gitUpdate(version, extractDir, params)
|
|
|
|
if not updated:
|
|
# Install the requested version.
|
|
let path = download(version, params)
|
|
defer:
|
|
# Delete downloaded file
|
|
discard tryRemoveFile(path)
|
|
# Make sure no stale files from previous installation exist.
|
|
removeDir(extractDir)
|
|
# Extract the downloaded file.
|
|
extract(path, extractDir)
|
|
|
|
# A "special" version is downloaded from GitHub and thus needs a `.git`
|
|
# directory in order to let `koch` know that it should download a "devel"
|
|
# Nimble.
|
|
if version.isSpecial:
|
|
gitInit(version, extractDir, params)
|
|
|
|
# Build the compiler
|
|
build(extractDir, version, params)
|
|
|
|
proc safeSwitchTo(version: Version, params: CliParams, wasInstalled: bool) =
|
|
try:
|
|
switchTo(version, params)
|
|
except Exception as exc:
|
|
# If we cannot switch to the newly installed version for whatever reason
|
|
# we assume the installation failed. This can happen for example on
|
|
# Windows when Windows Defender flags one of the binaries as a virus.
|
|
display("Exception:", exc.msg, Error, HighPriority)
|
|
# Perform clean up.
|
|
if not wasInstalled and not params.skipClean:
|
|
display("Cleaning", "failed install of " & $version, priority = HighPriority)
|
|
try:
|
|
removeDir(params.getInstallationDir(version))
|
|
except Exception as exc:
|
|
display("Warning:", "Cleaning failed: " & exc.msg, Warning)
|
|
|
|
raise newException(GetnimError, "Installation failed")
|
|
|
|
proc chooseVersion(version: string, params: CliParams) =
|
|
# Command is a version.
|
|
let version = parseVersion(version)
|
|
|
|
# Verify that C compiler is installed.
|
|
if params.needsCCInstall():
|
|
when defined(windows):
|
|
# Install MingW.
|
|
let path = downloadMingw(params)
|
|
extract(path, getMingwPath(params))
|
|
else:
|
|
let binName =
|
|
when defined(macosx):
|
|
"clang"
|
|
else:
|
|
"gcc"
|
|
|
|
raise newException(
|
|
GetnimError,
|
|
"No C compiler found. Nim compiler requires a C compiler.\n" &
|
|
"Install " & binName & " using your favourite package manager."
|
|
)
|
|
|
|
# Verify that DLLs (openssl primarily) are installed.
|
|
when defined(windows):
|
|
if params.needsDLLInstall():
|
|
# Install DLLs.
|
|
let
|
|
path = downloadDLLs(params)
|
|
tempDir = getTempDir() / "getnim-dlls"
|
|
binDir = getBinDir(params)
|
|
removeDir(tempDir)
|
|
createDir(tempDir)
|
|
extract(path, tempDir)
|
|
for kind, path in walkDir(tempDir, relative = true):
|
|
if kind == pcFile:
|
|
try:
|
|
if not fileExists(binDir / path) or
|
|
getLastModificationTime(binDir / path) < getLastModificationTime(tempDir / path):
|
|
moveFile(tempDir / path, binDir / path)
|
|
display("Info:", "Copied '$1' to '$2'" % [path, binDir], priority = HighPriority)
|
|
except:
|
|
display("Warning:", "Error copying '$1' to '$2': $3" % [path, binDir, getCurrentExceptionMsg()], Warning, priority = HighPriority)
|
|
removeDir(tempDir)
|
|
else:
|
|
display("Info:", "DLLs already installed", priority = MediumPriority)
|
|
|
|
var wasInstalled = params.isVersionInstalled(version)
|
|
if not wasInstalled:
|
|
installVersion(version, params)
|
|
|
|
safeSwitchTo(version, params, wasInstalled)
|
|
|
|
proc choose(params: CliParams) =
|
|
if dirExists(params.command):
|
|
# Command is a file path likely pointing to an existing Nim installation.
|
|
switchTo(params.command, params)
|
|
else:
|
|
# Check for release channel.
|
|
if params.command.isReleaseChannel():
|
|
let version = getChannelVersion(params.command, params)
|
|
|
|
chooseVersion(version, params)
|
|
pinChannelVersion(params.command, version, params)
|
|
setCurrentChannel(params.command, params)
|
|
else:
|
|
chooseVersion(params.command, params)
|
|
|
|
when defined(windows):
|
|
# Check and add ~/.nimble/bin to PATH
|
|
if not isNimbleBinInPath(params) and params.firstInstall:
|
|
setNimbleBinPath(params)
|
|
|
|
proc updateSelf(params: CliParams) =
|
|
display("Updating", "getnim", priority = HighPriority)
|
|
|
|
let version = getChannelVersion("self", params, live=true).newVersion
|
|
if not params.force and version <= getnimVersion.newVersion:
|
|
display("Info:", "Already up to date at version " & getnimVersion,
|
|
Success, HighPriority)
|
|
return
|
|
|
|
# https://stackoverflow.com/a/9163044/492186
|
|
let tag = "v" & $version
|
|
let filename = "getnim-" & $version & "_" & hostOS & "_" & hostCPU.addFileExt(ExeExt)
|
|
let url = "https://github.com/dom96/choosenim/releases/download/$1/$2" % [
|
|
tag, filename
|
|
]
|
|
let newFilename = getAppDir() / "getnim_new".addFileExt(ExeExt)
|
|
downloadFile(url, newFilename, params)
|
|
|
|
let appFilename = getAppFilename()
|
|
# Move getnim.exe to getnim_ver.exe
|
|
let oldFilename = "getnim_" & getnimVersion.addFileExt(ExeExt)
|
|
display("Info:", "Renaming '$1' to '$2'" % [appFilename, oldFilename])
|
|
moveFile(appFilename, getAppDir() / oldFilename)
|
|
|
|
# Move getnim_new.exe to getnim.exe
|
|
display("Info:", "Renaming '$1' to '$2'" % [newFilename, appFilename])
|
|
moveFile(newFilename, appFilename)
|
|
|
|
display("Info:", "Setting +x on downloaded file")
|
|
inclFilePermissions(appFilename, {fpUserExec, fpGroupExec})
|
|
|
|
display("Info:", "Updated getnim to version " & $version,
|
|
Success, HighPriority)
|
|
|
|
proc update(params: CliParams) =
|
|
if params.commands.len != 2:
|
|
raise newException(GetnimError,
|
|
"Expected 1 parameter to 'update' command")
|
|
|
|
let channel = params.commands[1]
|
|
if channel.toLowerAscii() == "self":
|
|
updateSelf(params)
|
|
return
|
|
|
|
display("Updating", channel, priority = HighPriority)
|
|
|
|
# Retrieve the current version for the specified channel.
|
|
let version = getChannelVersion(channel, params, live=true).newVersion
|
|
|
|
# Ensure that the version isn't already installed.
|
|
if not canUpdate(version, params):
|
|
display("Info:", "Already up to date at version " & $version,
|
|
Success, HighPriority)
|
|
pinChannelVersion(channel, $version, params)
|
|
if getSelectedVersion(params) != version:
|
|
switchTo(version, params)
|
|
return
|
|
|
|
# Make sure the archive is downloaded again if the version is special.
|
|
if version.isSpecial:
|
|
removeDir(params.getDownloadPath($version).splitFile.dir)
|
|
|
|
# Install the new version and pin it.
|
|
installVersion(version, params)
|
|
pinChannelVersion(channel, $version, params)
|
|
|
|
display("Updated", "to " & $version, Success, HighPriority)
|
|
|
|
# Always switch to the updated version.
|
|
safeSwitchTo(version, params, wasInstalled=false)
|
|
|
|
proc show(params: CliParams) =
|
|
let channel = getCurrentChannel(params)
|
|
let path = getSelectedPath(params)
|
|
let (_, version) = getNameVersion(path)
|
|
if params.commands.len == 2:
|
|
let whatToShow = params.commands[1]
|
|
if whatToShow.toLowerAscii() == "path":
|
|
echo path
|
|
return
|
|
if version != "":
|
|
display("Selected:", version, priority = HighPriority)
|
|
|
|
if channel.len > 0:
|
|
display("Channel:", channel, priority = HighPriority)
|
|
else:
|
|
display("Channel:", "No channel selected", priority = HighPriority)
|
|
|
|
display("Path:", path, priority = HighPriority)
|
|
|
|
var versions: seq[string] = @[]
|
|
for path in walkDirs(params.getInstallDir() & "/*"):
|
|
let (_, versionAvailable) = getNameVersion(path)
|
|
versions.add(versionAvailable)
|
|
|
|
if versions.len() > 1:
|
|
versions.sort(system.cmp, Descending)
|
|
if versions.contains("#head"):
|
|
versions.del(find(versions, "#head"))
|
|
versions.insert("#head", 0)
|
|
if versions.contains("#devel"):
|
|
versions.del(find(versions, "#devel"))
|
|
versions.insert("#devel", 0)
|
|
|
|
echo ""
|
|
display("Versions:", " ", priority = HighPriority)
|
|
for ver in versions:
|
|
if ver == version:
|
|
display("*", ver, Success, HighPriority)
|
|
else:
|
|
display("", ver, priority = HighPriority)
|
|
|
|
proc versions(params: CliParams) =
|
|
let currentChannel = getCurrentChannel(params)
|
|
let currentVersion = getCurrentVersion(params)
|
|
|
|
let specialVersions = getSpecialVersions(params)
|
|
let localVersions = getInstalledVersions(params)
|
|
|
|
let remoteVersions =
|
|
if params.onlyInstalled: @[]
|
|
else: getAvailableVersions(params)
|
|
|
|
proc isActiveTag(params: CliParams, version: Version): string =
|
|
let tag =
|
|
if version == currentVersion: "*"
|
|
else: " " # must have non-zero length, or won't be displayed
|
|
return tag
|
|
|
|
proc isLatestTag(params: CliParams, version: Version): string =
|
|
let tag =
|
|
if isLatestVersion(params, version): " (latest)"
|
|
else: ""
|
|
return tag
|
|
|
|
proc canUpdateTag(params: CliParams, channel: string): string =
|
|
let version = getChannelVersion(channel, params, live = (not params.onlyInstalled))
|
|
let channelVersion = parseVersion(version)
|
|
let tag =
|
|
if canUpdate(channelVersion, params): " (update available!)"
|
|
else: ""
|
|
return tag
|
|
|
|
#[ Display version information,now that it has been collected ]#
|
|
|
|
if currentChannel.len > 0:
|
|
display("Channel:", currentChannel & canUpdateTag(params, currentChannel), priority = HighPriority)
|
|
echo ""
|
|
|
|
# local versions
|
|
display("Installed:", " ", priority = HighPriority)
|
|
for version in localVersions:
|
|
let activeDisplay =
|
|
if version == currentVersion: Success
|
|
else: Message
|
|
display(isActiveTag(params, version), $version & isLatestTag(params, version), activeDisplay, priority = HighPriority)
|
|
for version in specialVersions:
|
|
display(isActiveTag(params, version), $version, priority = HighPriority)
|
|
echo ""
|
|
|
|
# if the "--installed" flag was passed, don't display remote versions as we didn't fetch data for them.
|
|
if (not params.onlyInstalled):
|
|
display("Available:", " ", priority = HighPriority)
|
|
for version in remoteVersions:
|
|
if not (version in localVersions):
|
|
display("", $version & isLatestTag(params, version), priority = HighPriority)
|
|
echo ""
|
|
|
|
proc remove(params: CliParams) =
|
|
if params.commands.len != 2:
|
|
raise newException(GetnimError,
|
|
"Expected 1 parameter to 'remove' command")
|
|
|
|
let version = params.commands[1].newVersion
|
|
|
|
let isInstalled = isVersionInstalled(params, version)
|
|
if not isInstalled:
|
|
raise newException(GetnimError,
|
|
"Version $1 is not installed." % $version)
|
|
|
|
if version == getCurrentVersion(params):
|
|
raise newException(GetnimError,
|
|
"Cannot remove current version.")
|
|
|
|
let extractDir = params.getInstallationDir(version)
|
|
removeDir(extractDir)
|
|
|
|
display("Info:", "Removed version " & $version,
|
|
Success, HighPriority)
|
|
|
|
|
|
proc performAction(params: CliParams) =
|
|
case params.command.normalize
|
|
of "update":
|
|
update(params)
|
|
of "show":
|
|
show(params)
|
|
of "versions":
|
|
versions(params)
|
|
of "remove":
|
|
remove(params)
|
|
else:
|
|
choose(params)
|
|
|
|
when isMainModule:
|
|
var error = ""
|
|
var hint = ""
|
|
var params = newCliParams(proxyExeMode = false)
|
|
try:
|
|
parseCliParams(params)
|
|
createDir(params.getnimDir)
|
|
performAction(params)
|
|
except NimbleError:
|
|
let currentExc = (ref NimbleError)(getCurrentException())
|
|
(error, hint) = getOutputInfo(currentExc)
|
|
|
|
if error.len > 0:
|
|
displayTip()
|
|
display("Error:", error, Error, HighPriority)
|
|
if hint.len > 0:
|
|
display("Hint:", hint, Warning, HighPriority)
|
|
quit(QuitFailure)
|