Merge branch 'master' of https://github.com/Araq/nimble into Araq-master

This commit is contained in:
Dominik Picheta 2015-11-07 16:01:19 +00:00
commit 6fcbffd1cc
7 changed files with 425 additions and 226 deletions

View file

@ -1,6 +1,6 @@
[Package]
name = "nimble"
version = "0.6.2"
version = "0.7.0"
author = "Dominik Picheta"
description = "Nim package manager."
license = "BSD"

View file

@ -10,6 +10,8 @@ import nimblepkg/packageinfo, nimblepkg/version, nimblepkg/tools,
nimblepkg/download, nimblepkg/config, nimblepkg/nimbletypes,
nimblepkg/publish
import compiler/options
when not defined(windows):
from posix import getpid
else:
@ -38,9 +40,10 @@ type
nimbleData: JsonNode ## Nimbledata.json
ActionType = enum
actionNil, actionUpdate, actionInit, actionPublish,
actionNil, actionUpdate, actionInit, actionDump, actionPublish,
actionInstall, actionSearch,
actionList, actionBuild, actionPath, actionUninstall, actionCompile
actionList, actionBuild, actionPath, actionUninstall, actionCompile,
actionCustom
Action = object
case typ: ActionType
@ -53,7 +56,7 @@ type
packages: seq[PkgTuple] # Optional only for actionInstall.
of actionSearch:
search: seq[string] # Search string.
of actionInit:
of actionInit, actionDump:
projName: string
of actionCompile:
file: string
@ -86,6 +89,8 @@ Commands:
[-i, --installed] Lists all installed packages.
path pkgname ... Shows absolute path to the installed packages
specified.
dump [pkgname] Outputs Nimble package information for
external tools.
Options:
-h, --help Print this help message.
@ -99,7 +104,7 @@ Options:
For more information read the Github readme:
https://github.com/nim-lang/nimble#readme
"""
nimbleVersion = "0.6.2"
nimbleVersion = "0.7.0"
defaultPackageURL =
"https://github.com/nim-lang/packages/raw/master/packages.json"
@ -112,21 +117,21 @@ proc writeVersion() =
[nimbleVersion, CompileDate, CompileTime])
quit(QuitSuccess)
proc getNimbleDir(options: Options): string =
options.config.nimbleDir
proc getNimbleDir(opt: Options): string =
opt.config.nimbleDir
proc getPkgsDir(options: Options): string =
options.config.nimbleDir / "pkgs"
proc getPkgsDir(opt: Options): string =
opt.config.nimbleDir / "pkgs"
proc getBinDir(options: Options): string =
options.config.nimbleDir / "bin"
proc getBinDir(opt: Options): string =
opt.config.nimbleDir / "bin"
proc prompt(options: Options, question: string): bool =
proc prompt(opt: Options, question: string): bool =
## Asks an interactive question and returns the result.
##
## The proc will return immediately without asking the user if the global
## forcePrompts has a value different than dontForcePrompt.
case options.forcePrompts
case opt.forcePrompts
of forcePromptYes:
echo(question & " -> [forced yes]")
return true
@ -144,11 +149,11 @@ proc prompt(options: Options, question: string): bool =
else:
return false
proc renameBabelToNimble(options: Options) {.deprecated.} =
proc renameBabelToNimble(opt: Options) {.deprecated.} =
let babelDir = getHomeDir() / ".babel"
let nimbleDir = getHomeDir() / ".nimble"
if dirExists(babelDir):
if options.prompt("Found deprecated babel package directory, would you " &
if opt.prompt("Found deprecated babel package directory, would you " &
"like to rename it to nimble?"):
copyDir(babelDir, nimbleDir)
copyFile(babelDir / "babeldata.json", nimbleDir / "nimbledata.json")
@ -163,9 +168,10 @@ proc parseCmdLine(): Options =
case kind
of cmdArgument:
if result.action.typ == actionNil:
case key
options.command = key
case key.normalize()
of "install", "path":
case key
case key.normalize()
of "install":
result.action.typ = actionInstall
of "path":
@ -184,6 +190,9 @@ proc parseCmdLine(): Options =
of "init":
result.action.typ = actionInit
result.action.projName = ""
of "dump":
result.action.typ = actionDump
result.action.projName = ""
of "update":
result.action.typ = actionUpdate
result.action.optionalURL = ""
@ -197,7 +206,8 @@ proc parseCmdLine(): Options =
result.action.packages = @[]
of "publish":
result.action.typ = actionPublish
else: writeHelp()
else:
result.action.typ = actionCustom
else:
case result.action.typ
of actionNil:
@ -215,7 +225,7 @@ proc parseCmdLine(): Options =
result.action.optionalURL = key
of actionSearch:
result.action.search.add(key)
of actionInit:
of actionInit, actionDump:
if result.action.projName != "":
raise newException(NimbleError,
"Can only initialize one package at a time.")
@ -264,18 +274,18 @@ proc parseCmdLine(): Options =
else:
result.nimbleData = %{"reverseDeps": newJObject()}
proc update(options: Options) =
proc update(opt: Options) =
## Downloads the package list from the specified URL.
##
## If the download is successful, the global didUpdatePackages is set to
## true. Otherwise an exception is raised on error.
let url =
if options.action.typ == actionUpdate and options.action.optionalURL != "":
options.action.optionalURL
if opt.action.typ == actionUpdate and opt.action.optionalURL != "":
opt.action.optionalURL
else:
defaultPackageURL
echo("Downloading package list from " & url)
downloadFile(url, options.getNimbleDir() / "packages.json")
downloadFile(url, opt.getNimbleDir() / "packages.json")
echo("Done.")
proc checkInstallFile(pkgInfo: PackageInfo,
@ -326,7 +336,7 @@ proc copyWithExt(origDir, currentDir, dest: string,
result.add copyFileD(path, changeRoot(origDir, dest, path))
proc copyFilesRec(origDir, currentDir, dest: string,
options: Options, pkgInfo: PackageInfo): HashSet[string] =
opt: Options, pkgInfo: PackageInfo): HashSet[string] =
## Copies all the required files, skips files specified in the .nimble file
## (PackageInfo).
## Returns a list of filepaths to files which have been installed.
@ -339,7 +349,7 @@ proc copyFilesRec(origDir, currentDir, dest: string,
for file in pkgInfo.installFiles:
let src = origDir / file
if not src.existsFile():
if options.prompt("Missing file " & src & ". Continue?"):
if opt.prompt("Missing file " & src & ". Continue?"):
continue
else:
quit(QuitSuccess)
@ -350,7 +360,7 @@ proc copyFilesRec(origDir, currentDir, dest: string,
# TODO: Allow skipping files inside dirs?
let src = origDir / dir
if not src.existsDir():
if options.prompt("Missing directory " & src & ". Continue?"):
if opt.prompt("Missing directory " & src & ". Continue?"):
continue
else:
quit(QuitSuccess)
@ -366,7 +376,7 @@ proc copyFilesRec(origDir, currentDir, dest: string,
# Create the dir.
createDir(changeRoot(origDir, dest, file))
result.incl copyFilesRec(origDir, file, dest, options, pkgInfo)
result.incl copyFilesRec(origDir, file, dest, opt, pkgInfo)
else:
let skip = pkgInfo.checkInstallFile(origDir, file)
@ -377,26 +387,26 @@ proc copyFilesRec(origDir, currentDir, dest: string,
result.incl copyFileD(pkgInfo.mypath,
changeRoot(pkgInfo.mypath.splitFile.dir, dest, pkgInfo.mypath))
proc saveNimbleData(options: Options) =
proc saveNimbleData(opt: Options) =
# TODO: This file should probably be locked.
writeFile(options.getNimbleDir() / "nimbledata.json",
pretty(options.nimbleData))
writeFile(opt.getNimbleDir() / "nimbledata.json",
pretty(opt.nimbleData))
proc addRevDep(options: Options, dep: tuple[name, version: string],
proc addRevDep(opt: Options, dep: tuple[name, version: string],
pkg: PackageInfo) =
# let depNameVer = dep.name & '-' & dep.version
if not options.nimbleData["reverseDeps"].hasKey(dep.name):
options.nimbleData["reverseDeps"][dep.name] = newJObject()
if not options.nimbleData["reverseDeps"][dep.name].hasKey(dep.version):
options.nimbleData["reverseDeps"][dep.name][dep.version] = newJArray()
if not opt.nimbleData["reverseDeps"].hasKey(dep.name):
opt.nimbleData["reverseDeps"][dep.name] = newJObject()
if not opt.nimbleData["reverseDeps"][dep.name].hasKey(dep.version):
opt.nimbleData["reverseDeps"][dep.name][dep.version] = newJArray()
let revDep = %{ "name": %pkg.name, "version": %pkg.version}
let thisDep = options.nimbleData["reverseDeps"][dep.name][dep.version]
let thisDep = opt.nimbleData["reverseDeps"][dep.name][dep.version]
if revDep notin thisDep:
thisDep.add revDep
proc removeRevDep(options: Options, pkg: PackageInfo) =
proc removeRevDep(opt: Options, pkg: PackageInfo) =
## Removes ``pkg`` from the reverse dependencies of every package.
proc remove(options: Options, pkg: PackageInfo, depTup: PkgTuple,
proc remove(opt: Options, pkg: PackageInfo, depTup: PkgTuple,
thisDep: JsonNode) =
for ver, val in thisDep:
if ver.newVersion in depTup.ver:
@ -410,16 +420,16 @@ proc removeRevDep(options: Options, pkg: PackageInfo) =
for depTup in pkg.requires:
if depTup.name.isURL():
# We sadly must go through everything in this case...
for key, val in options.nimbleData["reverseDeps"]:
options.remove(pkg, depTup, val)
for key, val in opt.nimbleData["reverseDeps"]:
opt.remove(pkg, depTup, val)
else:
let thisDep = options.nimbleData["reverseDeps"][depTup.name]
let thisDep = opt.nimbleData["reverseDeps"][depTup.name]
if thisDep.isNil: continue
options.remove(pkg, depTup, thisDep)
opt.remove(pkg, depTup, thisDep)
# Clean up empty objects/arrays
var newData = newJObject()
for key, val in options.nimbleData["reverseDeps"]:
for key, val in opt.nimbleData["reverseDeps"]:
if val.len != 0:
var newVal = newJObject()
for ver, elem in val:
@ -427,19 +437,19 @@ proc removeRevDep(options: Options, pkg: PackageInfo) =
newVal[ver] = elem
if newVal.len != 0:
newData[key] = newVal
options.nimbleData["reverseDeps"] = newData
opt.nimbleData["reverseDeps"] = newData
saveNimbleData(options)
saveNimbleData(opt)
proc install(packages: seq[PkgTuple],
options: Options,
opt: Options,
doPrompt = true): tuple[paths: seq[string], pkg: PackageInfo]
proc processDeps(pkginfo: PackageInfo, options: Options): seq[string] =
proc processDeps(pkginfo: PackageInfo, opt: Options): seq[string] =
## Verifies and installs dependencies.
##
## Returns the list of paths to pass to the compiler during build phase.
result = @[]
let pkglist = getInstalledPkgs(options.getPkgsDir())
let pkglist = getInstalledPkgs(opt.getPkgsDir())
var reverseDeps: seq[tuple[name, version: string]] = @[]
for dep in pkginfo.requires:
if dep.name == "nimrod" or dep.name == "nim":
@ -451,7 +461,7 @@ proc processDeps(pkginfo: PackageInfo, options: Options): seq[string] =
var pkg: PackageInfo
if not findPkg(pkglist, dep, pkg):
echo("None found, installing...")
let (paths, installedPkg) = install(@[(dep.name, dep.ver)], options)
let (paths, installedPkg) = install(@[(dep.name, dep.ver)], opt)
result.add(paths)
pkg = installedPkg # For addRevDep
@ -459,7 +469,7 @@ proc processDeps(pkginfo: PackageInfo, options: Options): seq[string] =
echo("Dependency already satisfied.")
result.add(pkg.mypath.splitFile.dir)
# Process the dependencies of this dependency.
result.add(processDeps(pkg, options))
result.add(processDeps(pkg, opt))
reverseDeps.add((pkg.name, pkg.version))
# Check if two packages of the same name (but different version) are listed
@ -477,8 +487,8 @@ proc processDeps(pkginfo: PackageInfo, options: Options): seq[string] =
# them added if the above errorenous condition occurs
# (unsatisfiable dependendencies).
for i in reverseDeps:
addRevDep(options, i, pkginfo)
saveNimbleData(options)
addRevDep(opt, i, pkginfo)
saveNimbleData(opt)
proc buildFromDir(pkgInfo: PackageInfo, paths: seq[string], forRelease: bool) =
## Builds a package as specified by ``pkgInfo``.
@ -505,7 +515,7 @@ proc saveNimbleMeta(pkgDestDir, url: string, filesInstalled: HashSet[string]) =
nimblemeta["files"].add(%changeRoot(pkgDestDir, "", file))
writeFile(pkgDestDir / "nimblemeta.json", $nimblemeta)
proc removePkgDir(dir: string, options: Options) =
proc removePkgDir(dir: string, opt: Options) =
## Removes files belonging to the package in ``dir``.
try:
var nimblemeta = parseFile(dir / "nimblemeta.json")
@ -525,12 +535,12 @@ proc removePkgDir(dir: string, options: Options) =
". Files not installed by nimble are present.")
except OSError, JsonParsingError:
echo("Error: Unable to read nimblemeta.json: ", getCurrentExceptionMsg())
if not options.prompt("Would you like to COMPLETELY remove ALL files " &
if not opt.prompt("Would you like to COMPLETELY remove ALL files " &
"in " & dir & "?"):
quit(QuitSuccess)
removeDir(dir)
proc installFromDir(dir: string, latest: bool, options: Options,
proc installFromDir(dir: string, latest: bool, opt: Options,
url: string): tuple[paths: seq[string], pkg: PackageInfo] =
## Returns where package has been installed to, together with paths
## to the packages this package depends on.
@ -538,10 +548,10 @@ proc installFromDir(dir: string, latest: bool, options: Options,
## ``processDeps`` to gather a list of paths to pass to the nim compiler.
var pkgInfo = getPkgInfo(dir)
let realDir = pkgInfo.getRealDir()
let binDir = options.getBinDir()
let pkgsDir = options.getPkgsDir()
let binDir = opt.getBinDir()
let pkgsDir = opt.getPkgsDir()
# Dependencies need to be processed before the creation of the pkg dir.
result.paths = processDeps(pkginfo, options)
result.paths = processDeps(pkginfo, opt)
echo("Installing ", pkginfo.name, "-", pkginfo.version)
@ -552,10 +562,10 @@ proc installFromDir(dir: string, latest: bool, options: Options,
let versionStr = (if latest: "" else: '-' & pkgInfo.version)
let pkgDestDir = pkgsDir / (pkgInfo.name & versionStr)
if existsDir(pkgDestDir) and existsFile(pkgDestDir / "nimblemeta.json"):
if not options.prompt(pkgInfo.name & versionStr &
if not opt.prompt(pkgInfo.name & versionStr &
" already exists. Overwrite?"):
quit(QuitSuccess)
removePkgDir(pkgDestDir, options)
removePkgDir(pkgDestDir, opt)
# Remove any symlinked binaries
for bin in pkgInfo.bin:
# TODO: Check that this binary belongs to the package being installed.
@ -575,7 +585,7 @@ proc installFromDir(dir: string, latest: bool, options: Options,
if pkgInfo.bin.len > 0:
createDir(binDir)
# Copy all binaries and files that are not skipped
filesInstalled = copyFilesRec(realDir, realDir, pkgDestDir, options,
filesInstalled = copyFilesRec(realDir, realDir, pkgDestDir, opt,
pkgInfo)
# Set file permissions to +x for all binaries built,
# and symlink them on *nix OS' to $nimbleDir/bin/
@ -607,7 +617,7 @@ proc installFromDir(dir: string, latest: bool, options: Options,
let dest = binDir / cleanBin.changeFileExt("cmd")
echo("Creating stub: ", pkgDestDir / bin, " -> ", dest)
var contents = "@"
if options.config.chcp:
if opt.config.chcp:
if fixChcp:
contents.add "chcp 65001 > nul && "
else: contents.add "chcp 65001 > nul\n@"
@ -620,7 +630,7 @@ proc installFromDir(dir: string, latest: bool, options: Options,
else:
{.error: "Sorry, your platform is not supported.".}
else:
filesInstalled = copyFilesRec(realDir, realDir, pkgDestDir, options,
filesInstalled = copyFilesRec(realDir, realDir, pkgDestDir, opt,
pkgInfo)
# Save a nimblemeta.json file.
@ -659,46 +669,46 @@ proc downloadPkg(url: string, verRange: VersionRange,
echo("Downloading ", url, " into ", downloadDir, " using ", downMethod, "...")
result = (downloadDir, doDownload(url, downloadDir, verRange, downMethod))
proc getDownloadInfo*(pv: PkgTuple, options: Options,
proc getDownloadInfo*(pv: PkgTuple, opt: Options,
doPrompt: bool): (DownloadMethod, string) =
if pv.name.isURL:
return (checkUrlType(pv.name), pv.name)
else:
var pkg: Package
if getPackage(pv.name, options.getNimbleDir() / "packages.json", pkg):
if getPackage(pv.name, opt.getNimbleDir() / "packages.json", pkg):
return (pkg.downloadMethod.getDownloadMethod(), pkg.url)
else:
# If package is not found give the user a chance to update
# package.json
if doPrompt and
options.prompt(pv.name & " not found in local packages.json, " &
opt.prompt(pv.name & " not found in local packages.json, " &
"check internet for updated packages?"):
update(options)
return getDownloadInfo(pv, options, doPrompt)
update(opt)
return getDownloadInfo(pv, opt, doPrompt)
else:
raise newException(NimbleError, "Package not found.")
proc install(packages: seq[PkgTuple],
options: Options,
opt: Options,
doPrompt = true): tuple[paths: seq[string], pkg: PackageInfo] =
if packages == @[]:
result = installFromDir(getCurrentDir(), false, options, "")
result = installFromDir(getCurrentDir(), false, opt, "")
else:
# If packages.json is not present ask the user if they want to download it.
if not existsFile(options.getNimbleDir / "packages.json"):
if not existsFile(opt.getNimbleDir / "packages.json"):
if doPrompt and
options.prompt("Local packages.json not found, download it from " &
opt.prompt("Local packages.json not found, download it from " &
"internet?"):
update(options)
update(opt)
else:
quit("Please run nimble update.", QuitFailure)
# Install each package.
for pv in packages:
let (meth, url) = getDownloadInfo(pv, options, doPrompt)
let (meth, url) = getDownloadInfo(pv, opt, doPrompt)
let (downloadDir, downloadVersion) = downloadPkg(url, pv.ver, meth)
try:
result = installFromDir(downloadDir, false, options, url)
result = installFromDir(downloadDir, false, opt, url)
except BuildFailed:
# The package failed to build.
# Check if we tried building a tagged version of the package.
@ -707,37 +717,37 @@ proc install(packages: seq[PkgTuple],
# If we tried building a tagged version of the package then
# ask the user whether they want to try building #head.
let promptResult = doPrompt and
options.prompt(("Build failed for '$1@$2', would you" &
opt.prompt(("Build failed for '$1@$2', would you" &
" like to try installing '$1@#head' (latest unstable)?") %
[pv.name, $downloadVersion])
if promptResult:
result = install(@[(pv.name, headVer)], options, doPrompt)
result = install(@[(pv.name, headVer)], opt, doPrompt)
else:
raise newException(BuildFailed,
"Aborting installation due to build failure")
else:
raise
proc build(options: Options) =
proc build(opt: Options) =
var pkgInfo = getPkgInfo(getCurrentDir())
let paths = processDeps(pkginfo, options)
let paths = processDeps(pkginfo, opt)
buildFromDir(pkgInfo, paths, false)
proc compile(options: Options) =
proc compile(opt: Options) =
var pkgInfo = getPkgInfo(getCurrentDir())
let paths = processDeps(pkginfo, options)
let paths = processDeps(pkginfo, opt)
let realDir = pkgInfo.getRealDir()
var args = ""
for path in paths: args.add("--path:\"" & path & "\" ")
for option in options.action.compileOptions:
for option in opt.action.compileOptions:
args.add(option & " ")
let bin = options.action.file
let bin = opt.action.file
let backend =
if options.action.backend.len > 0:
options.action.backend
if opt.action.backend.len > 0:
opt.action.backend
else:
pkgInfo.backend
@ -749,27 +759,27 @@ proc compile(options: Options) =
doCmd(getNimBin() & " $# --noBabelPath $# \"$#\"" %
[backend, args, bin])
proc search(options: Options) =
## Searches for matches in ``options.action.search``.
proc search(opt: Options) =
## Searches for matches in ``opt.action.search``.
##
## Searches are done in a case insensitive way making all strings lower case.
assert options.action.typ == actionSearch
if options.action.search == @[]:
assert opt.action.typ == actionSearch
if opt.action.search == @[]:
raise newException(NimbleError, "Please specify a search string.")
if not existsFile(options.getNimbleDir() / "packages.json"):
if not existsFile(opt.getNimbleDir() / "packages.json"):
raise newException(NimbleError, "Please run nimble update.")
let pkgList = getPackageList(options.getNimbleDir() / "packages.json")
let pkgList = getPackageList(opt.getNimbleDir() / "packages.json")
var found = false
template onFound: stmt =
echoPackage(pkg)
if options.queryVersions:
if opt.queryVersions:
echoPackageVersions(pkg)
echo(" ")
found = true
break
for pkg in pkgList:
for word in options.action.search:
for word in opt.action.search:
# Search by name.
if word.toLower() in pkg.name.toLower():
onFound()
@ -781,19 +791,19 @@ proc search(options: Options) =
if not found:
echo("No package found.")
proc list(options: Options) =
if not existsFile(options.getNimbleDir() / "packages.json"):
proc list(opt: Options) =
if not existsFile(opt.getNimbleDir() / "packages.json"):
raise newException(NimbleError, "Please run nimble update.")
let pkgList = getPackageList(options.getNimbleDir() / "packages.json")
let pkgList = getPackageList(opt.getNimbleDir() / "packages.json")
for pkg in pkgList:
echoPackage(pkg)
if options.queryVersions:
if opt.queryVersions:
echoPackageVersions(pkg)
echo(" ")
proc listInstalled(options: Options) =
proc listInstalled(opt: Options) =
var h = initTable[string, seq[string]]()
let pkgs = getInstalledPkgs(options.getPkgsDir())
let pkgs = getInstalledPkgs(opt.getPkgsDir())
for x in pkgs.items():
let
pName = x.pkginfo.name
@ -807,7 +817,7 @@ proc listInstalled(options: Options) =
type VersionAndPath = tuple[version: Version, path: string]
proc listPaths(options: Options) =
proc listPaths(opt: Options) =
## Loops over installing packages displaying their installed paths.
##
## If there are several packages installed, only the last one (the version
@ -816,25 +826,27 @@ proc listPaths(options: Options) =
## but at the end quits with a non zero exit error.
##
## On success the proc returns normally.
assert options.action.typ == actionPath
assert(not options.action.packages.isNil)
assert opt.action.typ == actionPath
assert(not opt.action.packages.isNil)
var errors = 0
for name, version in options.action.packages.items:
for name, version in opt.action.packages.items:
var installed: seq[VersionAndPath] = @[]
# There may be several, list all available ones and sort by version.
for kind, path in walkDir(options.getPkgsDir):
if kind != pcDir or not path.startsWith(options.getPkgsDir / name):
for kind, path in walkDir(opt.getPkgsDir):
if kind != pcDir or not path.startsWith(opt.getPkgsDir / name):
continue
let
nimScriptFile = path / name.addFileExt("nims")
babelFile = path / name.addFileExt("babel")
nimbleFile = path / name.addFileExt("nimble")
hasSpec = nimbleFile.existsFile or babelFile.existsFile
hasSpec = nimScriptFile.existsFile or
nimbleFile.existsFile or babelFile.existsFile
if hasSpec:
var pkgInfo = getPkgInfo(path)
var v: VersionAndPath
v.version = newVersion(pkgInfo.version)
v.path = options.getPkgsDir / (pkgInfo.name & '-' & pkgInfo.version)
v.path = opt.getPkgsDir / (pkgInfo.name & '-' & pkgInfo.version)
installed.add(v)
else:
echo "Warning: No .nimble file found for ", path
@ -849,56 +861,91 @@ proc listPaths(options: Options) =
raise newException(NimbleError,
"At least one of the specified packages was not found")
proc init(options: Options) =
proc guessAuthor(): string =
if dirExists(os.getCurrentDir() / ".git"):
let (output, exitCode) = doCmdEx("git config user.name")
if exitCode == 0:
return output.string.strip
return "Anonymous"
proc join(x: seq[PkgTuple]; y: string): string =
result = x[0][0] & " " & $x[0][1]
for i in 1 ..< x.len:
result.add y
result.add x[i][0] & " " & $x[i][1]
proc dump(opt: Options) =
let proj = addFileExt(opt.action.projName, NimsExt)
let p = if fileExists(proj): readPackageInfo((true, proj))
else: getPkgInfo(os.getCurrentDir())
echo "name: ", p.name.escape
echo "version: ", p.version.escape
echo "author: ", p.author.escape
echo "desc: ", p.description.escape
echo "license: ", p.license.escape
echo "skipDirs: ", p.skipDirs.join(", ").escape
echo "skipFiles: ", p.skipFiles.join(", ").escape
echo "skipExt: ", p.skipExt.join(", ").escape
echo "installDirs: ", p.installDirs.join(", ").escape
echo "installFiles: ", p.installFiles.join(", ").escape
echo "installExt: ", p.installExt.join(", ").escape
echo "requires: ", p.requires.join(", ").escape
echo "bin: ", p.bin.join(", ").escape
echo "binDir: ", p.binDir.escape
echo "srcDir: ", p.srcDir.escape
echo "backend: ", p.backend.escape
proc init(opt: Options) =
echo("Initializing new Nimble project!")
var
pkgName, fName: string = ""
outFile: File
if (options.action.projName != ""):
pkgName = options.action.projName
fName = pkgName & ".nimble"
if opt.action.projName != "":
pkgName = opt.action.projName
fName = pkgName & NimsExt
if (existsFile(os.getCurrentDir() / fName)):
raise newException(NimbleError, "Already have a nimble file.")
raise newException(NimbleError, "Already have a nimscript file.")
else:
echo("Enter a project name for this (blank to use working directory), " &
"Ctrl-C to abort:")
pkgName = readline(stdin)
if (pkgName == ""):
if pkgName == "":
pkgName = os.getCurrentDir().splitPath.tail
if (pkgName == ""):
if pkgName == "":
raise newException(NimbleError, "Could not get default file path.")
fName = pkgName & ".nimble"
fName = pkgName & NimsExt
# Now need to write out .nimble file with projName and other details
if (not existsFile(os.getCurrentDir() / fName) and
open(f=outFile, filename = fName, mode = fmWrite)):
outFile.writeln("[Package]")
outFile.writeln("name = \"" & pkgName & "\"")
outFile.writeln("version = \"0.1.0\"")
outFile.writeln("author = \"Anonymous\"")
outFile.writeln("description = \"New Nimble project for Nim\"")
outFile.writeln("license = \"MIT\"")
outFile.writeln("")
outFile.writeln("[Deps]")
outFile.writeln("Requires: \"nim >= 0.10.0\"")
close(outFile)
outFile.writeLine """# Package
version = "1.0.0"
author = $1
description = ""
license = "MIT"
# Dependencies
requires "nim >= "0.11.2"
""" % guessAuthor().escape()
close(outFile)
else:
raise newException(NimbleError, "Unable to open file " & fName &
" for writing: " & osErrorMsg(osLastError()))
proc uninstall(options: Options) =
if options.action.packages.len == 0:
proc uninstall(opt: Options) =
if opt.action.packages.len == 0:
raise newException(NimbleError,
"Please specify the package(s) to uninstall.")
var pkgsToDelete: seq[PackageInfo] = @[]
# Do some verification.
for pkgTup in options.action.packages:
for pkgTup in opt.action.packages:
echo("Looking for ", pkgTup.name, " (", $pkgTup.ver, ")...")
let installedPkgs = getInstalledPkgs(options.getPkgsDir())
let installedPkgs = getInstalledPkgs(opt.getPkgsDir())
var pkgList = findAllPkgs(installedPkgs, pkgTup)
if pkgList.len == 0:
raise newException(NimbleError, "Package not found")
@ -908,7 +955,7 @@ proc uninstall(options: Options) =
for pkg in pkgList:
# Check whether any packages depend on the ones the user is trying to
# uninstall.
let thisPkgsDep = options.nimbleData["reverseDeps"]{pkg.name}{pkg.version}
let thisPkgsDep = opt.nimbleData["reverseDeps"]{pkg.name}{pkg.version}
if not thisPkgsDep.isNil:
var reason = ""
if thisPkgsDep.len == 1:
@ -936,47 +983,52 @@ proc uninstall(options: Options) =
pkgNames.add pkg.name & " (" & pkg.version & ")"
# Let's confirm that the user wants these packages removed.
if not options.prompt("The following packages will be removed:\n " &
if not opt.prompt("The following packages will be removed:\n " &
pkgNames & "\nDo you wish to continue?"):
quit(QuitSuccess)
for pkg in pkgsToDelete:
# If we reach this point then the package can be safely removed.
removeRevDep(options, pkg)
removePkgDir(options.getPkgsDir / (pkg.name & '-' & pkg.version), options)
removeRevDep(opt, pkg)
removePkgDir(opt.getPkgsDir / (pkg.name & '-' & pkg.version), opt)
echo("Removed ", pkg.name, " (", $pkg.version, ")")
proc doAction(options: Options) =
if not existsDir(options.getNimbleDir()):
createDir(options.getNimbleDir())
if not existsDir(options.getPkgsDir):
createDir(options.getPkgsDir)
proc getCommand: string = options.command
case options.action.typ
of actionUpdate:
update(options)
of actionInstall:
discard install(options.action.packages, options)
of actionUninstall:
uninstall(options)
of actionSearch:
search(options)
of actionList:
if options.queryInstalled: listInstalled(options)
else: list(options)
of actionPath:
listPaths(options)
of actionBuild:
build(options)
of actionCompile:
compile(options)
of actionInit:
init(options)
of actionPublish:
proc doAction(opt: Options) =
if not existsDir(opt.getNimbleDir()):
createDir(opt.getNimbleDir())
if not existsDir(opt.getPkgsDir):
createDir(opt.getPkgsDir)
case getCommand().normalize
of "udpate":
update(opt)
of "install":
discard install(opt.action.packages, opt)
of "uninstall", "remove", "delete", "del", "rm":
uninstall(opt)
of "search":
search(opt)
of "list":
if opt.queryInstalled: listInstalled(opt)
else: list(opt)
of "path":
listPaths(opt)
of "build":
build(opt)
of "c", "compile", "js", "cpp", "cc":
compile(opt)
of "init":
init(opt)
of "publish":
var pkgInfo = getPkgInfo(getCurrentDir())
publish(pkgInfo)
of actionNil:
assert false
of "dump":
dump(opt)
of "nop": discard
else:
raise newException(NimbleError, "Unknown command: " & getCommand())
when isMainModule:
when defined(release):

View file

@ -1 +1,4 @@
-d:ssl
--path:"$lib/packages/docutils"
--noNimblePath
--path:"$nim/"

View file

@ -4,9 +4,9 @@
# recursive imports
import version
export version.NimbleError
type
NimbleError* = object of Exception
BuildFailed* = object of NimbleError
PackageInfo* = object
@ -27,4 +27,3 @@ type
binDir*: string
srcDir*: string
backend*: string

View file

@ -0,0 +1,117 @@
# Copyright (C) Andreas Rumpf. All rights reserved.
# BSD License. Look at license.txt for more info.
## Implements the new configuration system for Nimble. Uses Nim as a
## scripting language.
import
compiler/ast, compiler/modules, compiler/passes, compiler/passaux,
compiler/condsyms, compiler/options, compiler/sem, compiler/semdata,
compiler/llstream, compiler/vm, compiler/vmdef, compiler/commands,
compiler/msgs, compiler/magicsys, compiler/lists
from compiler/scriptconfig import setupVM
import nimbletypes, version
import os, strutils
proc raiseVariableError(ident, typ: string) {.noinline.} =
raise newException(NimbleError,
"NimScript's variable '" & ident & "' needs a value of type '" & typ & "'.")
proc isStrLit(n: PNode): bool = n.kind in {nkStrLit..nkTripleStrLit}
proc getGlobal(ident: string): string =
let n = vm.globalCtx.getGlobalValue(getSysSym ident)
if n.isStrLit:
result = if n.strVal.isNil: "" else: n.strVal
else:
raiseVariableError(ident, "string")
proc getGlobalAsSeq(ident: string): seq[string] =
let n = vm.globalCtx.getGlobalValue(getSysSym ident)
result = @[]
if n.kind == nkBracket:
for x in n:
if x.isStrLit:
result.add x.strVal
else:
raiseVariableError(ident, "seq[string]")
else:
raiseVariableError(ident, "seq[string]")
proc extractRequires(result: var seq[PkgTuple]) =
let n = vm.globalCtx.getGlobalValue(getSysSym "requiresData")
if n.kind == nkBracket:
for x in n:
if x.kind == nkPar and x.len == 2 and x[0].isStrLit and x[1].isStrLit:
result.add(parseRequires(x[0].strVal & x[1].strVal))
elif x.isStrLit:
result.add(parseRequires(x.strVal))
else:
raiseVariableError("requiresData", "seq[(string, VersionReq)]")
else:
raiseVariableError("requiresData", "seq[(string, VersionReq)]")
proc readPackageInfoFromNims*(scriptName: string; result: var PackageInfo) =
setDefaultLibpath()
passes.gIncludeFile = includeModule
passes.gImportModule = importModule
initDefines()
defineSymbol("nimscript")
defineSymbol("nimconfig")
defineSymbol("nimble")
registerPass(semPass)
registerPass(evalPass)
appendStr(searchPaths, options.libpath)
var m = makeModule(scriptName)
incl(m.flags, sfMainModule)
vm.globalCtx = setupVM(m, scriptName)
compileSystemModule()
processModule(m, llStreamOpen(scriptName, fmRead), nil)
template trivialField(field) =
result.field = getGlobal(astToStr field)
template trivialFieldSeq(field) =
result.field.add getGlobalAsSeq(astToStr field)
# keep reasonable default:
let name = getGlobal"packageName"
if name.len > 0: result.name = name
trivialField version
trivialField author
trivialField description
trivialField license
trivialField srcdir
trivialField bindir
trivialFieldSeq skipDirs
trivialFieldSeq skipFiles
trivialFieldSeq skipExt
trivialFieldSeq installDirs
trivialFieldSeq installFiles
trivialFieldSeq installExt
extractRequires result.requires
let binSeq = getGlobalAsSeq("bin")
for i in binSeq:
result.bin.add(i.addFileExt(ExeExt))
let backend = getGlobal("backend")
if backend.len == 0:
result.backend = "c"
elif cmpIgnoreStyle(backend, "javascript") == 0:
result.backend = "js"
else:
result.backend = backend.toLower()
# ensure everything can be called again:
resetAllModulesHard()
vm.globalCtx = nil
initDefines()

View file

@ -1,11 +1,14 @@
# Copyright (C) Dominik Picheta. All rights reserved.
# BSD License. Look at license.txt for more info.
import parsecfg, json, streams, strutils, parseutils, os
import version, tools, nimbletypes
import version, tools, nimbletypes, nimscriptsupport
when not declared(system.map):
from sequtils import map
const
NimsExt* = ".nims"
type
Package* = object
# Required fields in a package.
@ -15,7 +18,7 @@ type
downloadMethod*: string
description*: string
tags*: seq[string] # Even if empty, always a valid non nil seq. \
# From here on, optional fields set to the emtpy string if not available.
# From here on, optional fields set to the empty string if not available.
version*: string
dvcsTag*: string
web*: string # Info url for humans.
@ -23,9 +26,12 @@ type
MetaData* = object
url*: string
proc initPackageInfo(): PackageInfo =
result.mypath = ""
result.name = ""
NimbleFile* = tuple[isNimScript: bool; file: string]
proc initPackageInfo(path: string): PackageInfo =
result.mypath = path
# reasonable default:
result.name = path.splitFile.name
result.version = ""
result.author = ""
result.description = ""
@ -67,23 +73,6 @@ proc validatePackageInfo(pkgInfo: PackageInfo, path: string) =
"Version may only consist of numbers and the '.' character " &
"but found '" & c & "'.")
proc parseRequires(req: string): PkgTuple =
try:
if ' ' in req:
var i = skipUntil(req, Whitespace)
result.name = req[0 .. i].strip
result.ver = parseVersionRange(req[i .. req.len-1])
elif '#' in req:
var i = skipUntil(req, {'#'})
result.name = req[0 .. i-1]
result.ver = parseVersionRange(req[i .. req.len-1])
else:
result.name = req.strip
result.ver = VersionRange(kind: verAny)
except ParseVersionError:
raise newException(NimbleError,
"Unable to parse dependency version range: " & getCurrentExceptionMsg())
proc multiSplit(s: string): seq[string] =
## Returns ``s`` split by newline and comma characters.
##
@ -100,9 +89,7 @@ proc multiSplit(s: string): seq[string] =
if len(result) < 1:
return @[s]
proc readPackageInfo*(path: string): PackageInfo =
result = initPackageInfo()
result.mypath = path
proc readPackageInfoFromNimble(path: string; result: var PackageInfo) =
var fs = newFileStream(path, fmRead)
if fs != nil:
var p: CfgParser
@ -164,7 +151,35 @@ proc readPackageInfo*(path: string): PackageInfo =
close(p)
else:
raise newException(ValueError, "Cannot open package info: " & path)
validatePackageInfo(result, path)
proc getNameVersion*(pkgpath: string): tuple[name, version: string] =
## Splits ``pkgpath`` in the format ``/home/user/.nimble/pkgs/package-0.1``
## into ``(packagea, 0.1)``
result.name = ""
result.version = ""
let tail = pkgpath.splitPath.tail
if '-' notin tail:
result.name = tail
return
for i in countdown(tail.len-1, 0):
if tail[i] == '-':
result.name = tail[0 .. i-1]
result.version = tail[i+1 .. tail.len-1]
break
proc readPackageInfo*(nf: NimbleFile; onlyMinimalInfo=false): PackageInfo =
result = initPackageInfo(nf.file)
if nf.isNimScript:
if onlyMinimalInfo:
let tmp = getNameVersion(nf.file)
result.name = tmp.name
result.version = tmp.version
else:
readPackageInfoFromNims(nf.file, result)
else:
readPackageInfoFromNimble(nf.file, result)
validatePackageInfo(result, nf.file)
proc optionalField(obj: JsonNode, name: string, default = ""): string =
## Queries ``obj`` for the optional ``name`` string.
@ -241,21 +256,34 @@ proc getPackageList*(packagesPath: string): seq[Package] =
let pkg: Package = p.fromJson()
result.add(pkg)
proc findNimbleFile*(dir: string): string =
result = ""
proc findNimbleFile*(dir: string; error: bool): NimbleFile =
result = (false, "")
var hits = 0
for kind, path in walkDir(dir):
if kind == pcFile and path.splitFile.ext in [".babel", ".nimble"]:
if result != "":
raise newException(NimbleError,
"Only one .nimble file should be present in " & dir)
result = path
if kind == pcFile:
let ext = path.splitFile.ext
case ext
of ".babel", ".nimble":
result = (false, path)
inc hits
of NimsExt:
result = (true, path)
inc hits
else: discard
if hits >= 2:
raise newException(NimbleError,
"Only one .nimble file should be present in " & dir)
elif hits == 0:
if error:
raise newException(NimbleError,
"Specified directory does not contain a .nimble file.")
else:
# TODO: Abstract logging.
echo("WARNING: No .nimble file found for ", dir)
proc getPkgInfo*(dir: string): PackageInfo =
## Find the .nimble file in ``dir`` and parses it, returning a PackageInfo.
let nimbleFile = findNimbleFile(dir)
if nimbleFile == "":
raise newException(NimbleError,
"Specified directory does not contain a .nimble file.")
let nimbleFile = findNimbleFile(dir, true)
result = readPackageInfo(nimbleFile)
proc getInstalledPkgs*(libsDir: string):
@ -266,13 +294,10 @@ proc getInstalledPkgs*(libsDir: string):
result = @[]
for kind, path in walkDir(libsDir):
if kind == pcDir:
let nimbleFile = findNimbleFile(path)
if nimbleFile != "":
let nimbleFile = findNimbleFile(path, false)
if nimbleFile.file != "":
let meta = readMetaData(path)
result.add((readPackageInfo(nimbleFile), meta))
else:
# TODO: Abstract logging.
echo("WARNING: No .nimble file found for ", path)
result.add((readPackageInfo(nimbleFile, true), meta))
proc findPkg*(pkglist: seq[tuple[pkginfo: PackageInfo, meta: MetaData]],
dep: PkgTuple,
@ -284,8 +309,8 @@ proc findPkg*(pkglist: seq[tuple[pkginfo: PackageInfo, meta: MetaData]],
##
## **Note**: dep.name here could be a URL, hence the need for pkglist.meta.
for pkg in pkglist:
if pkg.pkginfo.name.normalize != dep.name.normalize and
pkg.meta.url.normalize != dep.name.normalize: continue
if cmpIgnoreStyle(pkg.pkginfo.name, dep.name) != 0 and
cmpIgnoreStyle(pkg.meta.url, dep.name) != 0: continue
if withinRange(newVersion(pkg.pkginfo.version), dep.ver):
if not result or newVersion(r.version) < newVersion(pkg.pkginfo.version):
r = pkg.pkginfo
@ -298,8 +323,8 @@ proc findAllPkgs*(pkglist: seq[tuple[pkginfo: PackageInfo, meta: MetaData]],
## packages if multiple are found.
result = @[]
for pkg in pkglist:
if pkg.pkginfo.name.normalize != dep.name.normalize and
pkg.meta.url.normalize != dep.name.normalize: continue
if cmpIgnoreStyle(pkg.pkginfo.name, dep.name) != 0 and
cmpIgnoreStyle(pkg.meta.url, dep.name) != 0: continue
if withinRange(newVersion(pkg.pkginfo.version), dep.ver):
result.add pkg.pkginfo
@ -318,22 +343,6 @@ proc getOutputDir*(pkgInfo: PackageInfo, bin: string): string =
else:
result = pkgInfo.mypath.splitFile.dir / bin
proc getNameVersion*(pkgpath: string): tuple[name, version: string] =
## Splits ``pkgpath`` in the format ``/home/user/.nimble/pkgs/package-0.1``
## into ``(packagea, 0.1)``
result.name = ""
result.version = ""
let tail = pkgpath.splitPath.tail
if '-' notin tail:
result.name = tail
return
for i in countdown(tail.len-1, 0):
if tail[i] == '-':
result.name = tail[0 .. i-1]
result.version = tail[i+1 .. tail.len-1]
break
proc echoPackage*(pkg: Package) =
echo(pkg.name & ":")
echo(" url: " & pkg.url & " (" & pkg.downloadMethod & ")")

View file

@ -33,6 +33,7 @@ type
PkgTuple* = tuple[name: string, ver: VersionRange]
ParseVersionError* = object of ValueError
NimbleError* = object of Exception
proc newVersion*(ver: string): Version = return Version(ver)
proc newSpecial*(spe: string): Special = return Special(spe)
@ -198,6 +199,24 @@ proc parseVersionRange*(s: string): VersionRange =
"Unexpected char in version range: " & s[i])
inc(i)
proc parseRequires*(req: string): PkgTuple =
try:
if ' ' in req:
var i = skipUntil(req, Whitespace)
result.name = req[0 .. i].strip
result.ver = parseVersionRange(req[i .. req.len-1])
elif '#' in req:
var i = skipUntil(req, {'#'})
result.name = req[0 .. i-1]
result.ver = parseVersionRange(req[i .. req.len-1])
else:
result.name = req.strip
result.ver = VersionRange(kind: verAny)
except ParseVersionError:
raise newException(NimbleError,
"Unable to parse dependency version range: " & getCurrentExceptionMsg())
proc `$`*(verRange: VersionRange): string =
case verRange.kind
of verLater: