230 lines
7.6 KiB
Nim
230 lines
7.6 KiB
Nim
import parser, version, osproc, strutils, re, os, parseutils, streams
|
|
|
|
type
|
|
EInstall = object of EBase
|
|
|
|
TDepend = tuple[name: String, verRange: PVersionRange]
|
|
|
|
var debug* = 1 ## 0 = no messages, 1 - 3 = less to more verbose respectively.
|
|
|
|
proc echoD*(s: string, verbosity: int = 1, nl: bool = True) =
|
|
if debug >= verbosity:
|
|
stdout.write(s & (if nl: "\n" else: ""))
|
|
|
|
proc getBabelDir(): string =
|
|
when defined(windows):
|
|
result = getHomeDir() / "babel"
|
|
else:
|
|
result = getHomeDir() / ".babel"
|
|
|
|
proc getNimVersion(cmd: string = "nimrod"): String =
|
|
var output = execProcess(cmd & " -v")
|
|
var line = splitLines(output)[0]
|
|
|
|
# Thanks Araq :)
|
|
var i = 0
|
|
var nimrodVersion = ""
|
|
i = skipIgnoreCase(line, "Nimrod Compiler Version ")
|
|
if i <= 0: raise newException(EInstall, "Cannot detect Nimrod's version")
|
|
i = parseToken(line, nimrodVersion, {'.', '0'..'9'}, i)
|
|
if nimrodVersion.len == 0:
|
|
raise newException(EInstall, "Cannot detect Nimrod's version")
|
|
|
|
return nimrodVersion
|
|
|
|
proc compile*(file: string, flags: string = "") =
|
|
var args: string = flags & "c " & file
|
|
echoD("Compiling " & file & "...")
|
|
var code = execShellCmd(findExe("nimrod") & " " & args)
|
|
if code != quitSuccess:
|
|
raise newException(EInstall, "Compilation failed: Nimrod returned exit code " &
|
|
$code)
|
|
|
|
proc dependExists(name: string, verRange: PVersionRange): Bool =
|
|
if name == "nimrod":
|
|
var nimVer = getNimVersion()
|
|
if not withinRange(newVersion(nimVer), verRange):
|
|
raise newException(EInstall, "Nimrod version doesn't satisfy dependency: " &
|
|
nimVer & " " & $verRange)
|
|
else: return True
|
|
else:
|
|
for kind, path in walkDir(getBabelDir() / "packages"):
|
|
if kind == pcFile:
|
|
var file = path.extractFilename()
|
|
if file.startswith(name & "-"):
|
|
if verRange.kind != verAny:
|
|
var ver = ""
|
|
# This will have a dot at the end, it doesn't cause trouble though.
|
|
var ret = file.parseToken(ver, digits + {'.'}, name.len() + 1)
|
|
if ret != 0:
|
|
if withinRange(newVersion(ver), verRange):
|
|
return True
|
|
else:
|
|
raise newException(EInstall, "Invalid .babel file: " & file)
|
|
else: return True
|
|
|
|
return False
|
|
|
|
proc verifyDepends(proj: TProject): seq[TDepend] =
|
|
result = @[]
|
|
for nameStr, verStr in items(proj.depends):
|
|
echoD(" " & nameStr & " " & verStr & "...", 2, False)
|
|
var verRange: PVersionRange
|
|
if verStr == "":
|
|
new(verRange)
|
|
verRange.kind = verAny
|
|
else:
|
|
verRange = parseVersionRange(verStr)
|
|
|
|
if not dependExists(nameStr, verRange):
|
|
result.add((nameStr, verRange))
|
|
echoD(" FAIL", 2)
|
|
else:
|
|
echoD(" OK", 2)
|
|
|
|
proc createDirDebug(dir: string) =
|
|
# Have to check, so that it doesn't echo...
|
|
if not existsDir(dir):
|
|
echoD("Creating directory " & dir & "...", 3, False)
|
|
createDir(dir)
|
|
echoD(" Done!", 3)
|
|
|
|
proc createDirs(dirs: seq[string]) =
|
|
for i in items(dirs):
|
|
createDirDebug(i)
|
|
|
|
proc copyFileDebug(file, copyTo: string) =
|
|
echoD("Copying " & file & " to " & copyTo & "...", 3, False)
|
|
copyFile(file, copyTo)
|
|
echoD(" Done!", 3)
|
|
|
|
proc moveFileDebug(file, moveTo: string) =
|
|
echoD("Moving " & file & " to " & moveTo & "...", 3, False)
|
|
moveFile(file, moveTo)
|
|
echoD(" Done!", 3)
|
|
|
|
proc copyFiles(proj: TProject) =
|
|
# This will create a $home/.babel and lib/ or bin/. It will also copy all the
|
|
# files listed in proj.modules and proj.files and the .babel file.
|
|
var babelDir = getBabelDir()
|
|
|
|
var dirs = @[babelDir, babelDir / "lib", babelDir / "bin", babelDir / "packages"]
|
|
createDirs(dirs)
|
|
if proj.library:
|
|
# TODO: How will we handle multiple versions?
|
|
var projDir = babelDir / "lib" / proj.name # $babel/lib/name
|
|
createDirDebug(projDir)
|
|
# Copy the files
|
|
for i in items(proj.modules):
|
|
var file = proj.confDir / i.addFileExt("nim")
|
|
var copyTo = projDir / i.addFileExt("nim")
|
|
copyFileDebug(file, copyTo)
|
|
if proj.files.len > 0:
|
|
for i in items(proj.files):
|
|
var file = proj.confDir / i
|
|
var copyTo = projDir / i
|
|
copyFileDebug(file, copyTo)
|
|
|
|
elif proj.executable:
|
|
var exeSrcFile = proj.confDir / proj.exeFile.addFileExt("nim")
|
|
# Compile
|
|
compile(exeSrcFile)
|
|
|
|
var exeCmpFile = exeSrcFile.changeFileExt(ExeExt)
|
|
var copyTo = babelDir / "bin" / proj.exeFile.addFileExt(ExeExt)
|
|
copyFileDebug(exeCmpFile, copyTo)
|
|
|
|
# Copy the .babel file into the packages folder.
|
|
var babelFile = proj.confDir / proj.name.addFileExt("babel")
|
|
# addFileExt fails in this situation. Although it's fine without .babel
|
|
var copyTo = babelDir / "packages" /
|
|
(proj.name & "-" & proj.version)
|
|
copyFileDebug(babelFile, copyTo)
|
|
|
|
|
|
|
|
proc upgrade(proj: TProject) =
|
|
## This proc moves the files in lib/package into lib/package/lastVersion
|
|
## It then copies the new version into lib/package.
|
|
|
|
var babelDir = getBabelDir()
|
|
# Find the config for the latest (currently installed) package version in
|
|
# packages/
|
|
var latestVersion = "0"
|
|
var path = ""
|
|
for kind, confPath in walkDir(babelDir / "packages"):
|
|
if kind == pcFile:
|
|
var file = confPath.extractFilename()
|
|
if file.startsWith(proj.name & "-"):
|
|
var ver = ""
|
|
# This will have a dot at the end, it doesn't cause trouble though.
|
|
discard file.parseToken(ver, digits + {'.'}, proj.name.len() + 1)
|
|
if ver > latestVersion:
|
|
latestVersion = ver
|
|
path = confPath
|
|
|
|
assert(path != "")
|
|
|
|
if proj.library:
|
|
echoD("Reading " & path & "...", 3)
|
|
var latestConf = parseBabel(path)
|
|
var newVerDir = babelDir / "lib" / latestConf.name / latestConf.version
|
|
createDirDebug(newVerDir)
|
|
# Move the files
|
|
for kind, path in walkDir(babelDir / "lib" / latestConf.name):
|
|
if kind == pcFile:
|
|
moveFileDebug(path, newVerDir / path.extractFilename())
|
|
|
|
# Install the new version
|
|
copyFiles(proj)
|
|
|
|
|
|
proc install*(name: string, filename: string = "") =
|
|
## Install package by the name of ``name``, filename specifies where to look for it
|
|
## if left as "", the current working directory will be assumed.
|
|
# TODO: Add a `debug` variable? If true the status messages get echo-ed,
|
|
# vice-versa if false?
|
|
var babelFile: TProject
|
|
var path = ""
|
|
if filename == "":
|
|
path = name.addFileExt("babel")
|
|
else:
|
|
path = filename / name.addFileExt("babel")
|
|
|
|
echoD("Reading " & path & "...", 3)
|
|
babelFile = parseBabel(path)
|
|
|
|
var ret = babelFile.verify()
|
|
if ret != "":
|
|
raise newException(EInstall, "Verifying the .babel file failed: " & ret)
|
|
|
|
var upgrade = False # Specifies whether to upgrade
|
|
# Check whether this package is already installed
|
|
if dependExists(babelFile.name, newVREq(babelFile.version)):
|
|
raise newException(EInstall,
|
|
"This package is already installed!")
|
|
elif dependExists(babelFile.name, newVREarlier(babelFile.version)):
|
|
upgrade = True
|
|
|
|
if babelFile.depends.len == 1:
|
|
echoD("Verifying 1 dependency...")
|
|
else:
|
|
echoD("Verifying " & $babelFile.depends.len() & " dependencies...")
|
|
var dependsNeeded = babelFile.verifyDepends()
|
|
if dependsNeeded.len() > 0:
|
|
raise newException(EInstall, "TODO: Download & Install dependencies.")
|
|
else:
|
|
echoD("All dependencies verified!")
|
|
|
|
if not upgrade:
|
|
echoD("Installing " & babelFile.name & "-" & babelFile.version & "...")
|
|
babelFile.copyFiles()
|
|
else:
|
|
echoD("Upgrading " & babelFile.name & " to version " &
|
|
babelFile.version & "...")
|
|
babelFile.upgrade()
|
|
|
|
echoD("Package " & babelFile.name & " successfully installed.")
|
|
|
|
when isMainModule:
|
|
install(paramStr(1))
|