Fixes #373. Implements #287. Fixes #271.

This commit is contained in:
Dominik Picheta 2017-09-02 23:19:03 +01:00
commit 92e9ec5e59
6 changed files with 203 additions and 74 deletions

View file

@ -13,7 +13,7 @@ from sequtils import toSeq
import nimblepkg/packageinfo, nimblepkg/version, nimblepkg/tools,
nimblepkg/download, nimblepkg/config, nimblepkg/common,
nimblepkg/publish, nimblepkg/options, nimblepkg/packageparser,
nimblepkg/cli, nimblepkg/packageinstaller
nimblepkg/cli, nimblepkg/packageinstaller, nimblepkg/reversedeps
import nimblepkg/nimscriptsupport
@ -141,61 +141,6 @@ proc copyFilesRec(origDir, currentDir, dest: string,
result.incl copyFileD(pkgInfo.mypath,
changeRoot(pkgInfo.mypath.splitFile.dir, dest, pkgInfo.mypath))
proc saveNimbleData(options: Options) =
# TODO: This file should probably be locked.
writeFile(options.getNimbleDir() / "nimbledata.json",
pretty(options.nimbleData))
proc addRevDep(options: 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()
let revDep = %{ "name": %pkg.name, "version": %pkg.specialVersion}
let thisDep = options.nimbleData["reverseDeps"][dep.name][dep.version]
if revDep notin thisDep:
thisDep.add revDep
proc removeRevDep(options: Options, pkg: PackageInfo) =
## Removes ``pkg`` from the reverse dependencies of every package.
assert(not pkg.isMinimal)
proc remove(options: Options, pkg: PackageInfo, depTup: PkgTuple,
thisDep: JsonNode) =
for ver, val in thisDep:
if ver.newVersion in depTup.ver:
var newVal = newJArray()
for revDep in val:
if not (revDep["name"].str == pkg.name and
revDep["version"].str == pkg.specialVersion):
newVal.add revDep
thisDep[ver] = newVal
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)
else:
let thisDep = options.nimbleData{"reverseDeps", depTup.name}
if thisDep.isNil: continue
options.remove(pkg, depTup, thisDep)
# Clean up empty objects/arrays
var newData = newJObject()
for key, val in options.nimbleData["reverseDeps"]:
if val.len != 0:
var newVal = newJObject()
for ver, elem in val:
if elem.len != 0:
newVal[ver] = elem
if newVal.len != 0:
newData[key] = newVal
options.nimbleData["reverseDeps"] = newData
saveNimbleData(options)
proc install(packages: seq[PkgTuple],
options: Options,
doPrompt = true): tuple[deps: seq[PackageInfo], pkg: PackageInfo]
@ -270,7 +215,7 @@ proc processDeps(pkginfo: PackageInfo, options: Options): seq[PackageInfo] =
# (unsatisfiable dependendencies).
# N.B. NimbleData is saved in installFromDir.
for i in reverseDeps:
addRevDep(options, i, pkginfo)
addRevDep(options.nimbleData, i, pkginfo)
proc buildFromDir(pkgInfo: PackageInfo, paths: seq[string],
args: var seq[string]) =
@ -416,6 +361,11 @@ proc installFromDir(dir: string, requestedVer: VersionRange, options: Options,
[pkgInfo.name, pkgInfo.specialVersion]
if not options.prompt(msg):
raise NimbleQuit(msg: "")
# Remove reverse deps.
let pkgInfo = getPkgInfo(pkgDestDir, options)
options.nimbleData.removeRevDep(pkgInfo)
removePkgDir(pkgDestDir, options)
# Remove any symlinked binaries
for bin in pkgInfo.bin:
@ -426,7 +376,6 @@ proc installFromDir(dir: string, requestedVer: VersionRange, options: Options,
else:
removeFile(binDir / bin)
createDir(pkgDestDir)
# Copy this package's files based on the preferences specified in PkgInfo.
var filesInstalled = initSet[string]()
@ -831,21 +780,20 @@ 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.specialVersion}
if not thisPkgsDep.isNil:
var reason = ""
if thisPkgsDep.len == 1:
reason = "$1 ($2) depends on it" % [thisPkgsDep[0]["name"].str,
thisPkgsDep[0]["version"].str]
else:
for i in 0 .. <thisPkgsDep.len:
reason.add("$1 ($2)" % [thisPkgsDep[i]["name"].str,
thisPkgsDep[i]["version"].str])
if i != <thisPkgsDep.len:
reason.add ", "
reason.add " depend on it"
errors.add("Cannot uninstall $1 ($2) because $3" % [pkgTup.name,
pkg.specialVersion, reason])
let revDeps = getRevDeps(options, pkg)
var reason = ""
if revDeps.len == 1:
reason = "$1 ($2) depends on it" % [revDeps[0].name, $revDeps[0].ver]
else:
for i in 0 .. <revDeps.len:
reason.add("$1 ($2)" % [revDeps[i].name, $revDeps[i].ver])
if i != <revDeps.len:
reason.add ", "
reason.add " depend on it"
if revDeps.len > 0:
errors.add("Cannot uninstall $1 ($2) because $3" %
[pkgTup.name, pkg.specialVersion, reason])
else:
pkgsToDelete.add pkg
@ -869,12 +817,14 @@ proc uninstall(options: Options) =
# removeRevDep needs the package dependency info, so we can't just pass
# a minimal pkg info.
removeRevDep(options, pkg.toFullInfo(options))
removeRevDep(options.nimbleData, pkg.toFullInfo(options))
removePkgDir(options.getPkgsDir / (pkg.name & '-' & pkg.specialVersion),
options)
display("Removed", "$1 ($2)" % [pkg.name, $pkg.specialVersion], Success,
HighPriority)
saveNimbleData(options)
proc listTasks(options: Options) =
let nimbleFile = findNimbleFile(getCurrentDir(), true)
nimscriptsupport.listTasks(nimbleFile, options)

View file

@ -0,0 +1,115 @@
# Copyright (C) Dominik Picheta. All rights reserved.
# BSD License. Look at license.txt for more info.
import os, json
import options, common, version, download, packageinfo
proc saveNimbleData*(options: Options) =
# TODO: This file should probably be locked.
writeFile(options.getNimbleDir() / "nimbledata.json",
pretty(options.nimbleData))
proc addRevDep*(nimbleData: JsonNode, dep: tuple[name, version: string],
pkg: PackageInfo) =
# Add a record which specifies that `pkg` has a dependency on `dep`, i.e.
# the reverse dependency of `dep` is `pkg`.
if not nimbleData["reverseDeps"].hasKey(dep.name):
nimbleData["reverseDeps"][dep.name] = newJObject()
if not nimbleData["reverseDeps"][dep.name].hasKey(dep.version):
nimbleData["reverseDeps"][dep.name][dep.version] = newJArray()
let revDep = %{ "name": %pkg.name, "version": %pkg.specialVersion}
let thisDep = nimbleData["reverseDeps"][dep.name][dep.version]
if revDep notin thisDep:
thisDep.add revDep
proc removeRevDep*(nimbleData: JsonNode, pkg: PackageInfo) =
## Removes ``pkg`` from the reverse dependencies of every package.
assert(not pkg.isMinimal)
proc remove(pkg: PackageInfo, depTup: PkgTuple, thisDep: JsonNode) =
for ver, val in thisDep:
if ver.newVersion in depTup.ver:
var newVal = newJArray()
for revDep in val:
if not (revDep["name"].str == pkg.name and
revDep["version"].str == pkg.specialVersion):
newVal.add revDep
thisDep[ver] = newVal
for depTup in pkg.requires:
if depTup.name.isURL():
# We sadly must go through everything in this case...
for key, val in nimbleData["reverseDeps"]:
remove(pkg, depTup, val)
else:
let thisDep = nimbleData{"reverseDeps", depTup.name}
if thisDep.isNil: continue
remove(pkg, depTup, thisDep)
# Clean up empty objects/arrays
var newData = newJObject()
for key, val in nimbleData["reverseDeps"]:
if val.len != 0:
var newVal = newJObject()
for ver, elem in val:
if elem.len != 0:
newVal[ver] = elem
if newVal.len != 0:
newData[key] = newVal
nimbleData["reverseDeps"] = newData
proc getRevDeps*(options: Options, pkg: PackageInfo): seq[PkgTuple] =
## Returns a list of *currently installed* reverse dependencies for `pkg`.
result = @[]
let thisPkgsDep =
options.nimbleData["reverseDeps"]{pkg.name}{pkg.specialVersion}
if not thisPkgsDep.isNil:
let pkgList = getInstalledPkgsMin(options.getPkgsDir(), options)
for pkg in thisPkgsDep:
let pkgTup = (
name: pkg["name"].getStr(),
ver: parseVersionRange(pkg["version"].getStr())
)
var pkgInfo: PackageInfo
if not findPkg(pkgList, pkgTup, pkgInfo):
continue
result.add(pkgTup)
when isMainModule:
var nimbleData = %{"reverseDeps": newJObject()}
let nimforum1 = PackageInfo(
isMinimal: false,
name: "nimforum",
specialVersion: "0.1.0",
requires: @[("jester", parseVersionRange("0.1.0")),
("captcha", parseVersionRange("1.0.0")),
("auth", parseVersionRange("#head"))]
)
let nimforum2 = PackageInfo(isMinimal: false, name: "nimforum", specialVersion: "0.2.0")
let play = PackageInfo(isMinimal: false, name: "play", specialVersion: "#head")
nimbleData.addRevDep(("jester", "0.1.0"), nimforum1)
nimbleData.addRevDep(("jester", "0.1.0"), play)
nimbleData.addRevDep(("captcha", "1.0.0"), nimforum1)
nimbleData.addRevDep(("auth", "#head"), nimforum1)
nimbleData.addRevDep(("captcha", "1.0.0"), nimforum2)
nimbleData.addRevDep(("auth", "#head"), nimforum2)
doAssert nimbleData["reverseDeps"]["jester"]["0.1.0"].len == 2
doAssert nimbleData["reverseDeps"]["captcha"]["1.0.0"].len == 2
doAssert nimbleData["reverseDeps"]["auth"]["#head"].len == 2
block:
nimbleData.removeRevDep(nimforum1)
let jester = nimbleData["reverseDeps"]["jester"]["0.1.0"][0]
doAssert jester["name"].getStr() == play.name
doAssert jester["version"].getStr() == play.specialVersion
let captcha = nimbleData["reverseDeps"]["captcha"]["1.0.0"][0]
doAssert captcha["name"].getStr() == nimforum2.name
doAssert captcha["version"].getStr() == nimforum2.specialVersion
echo("Everything works!")

View file

@ -0,0 +1,10 @@
# Package
version = "0.1.0"
author = "Dominik Picheta"
description = "Random dep"
license = "MIT"
# Dependencies
requires "nim >= 0.15.0"

View file

@ -0,0 +1,11 @@
# Package
version = "0.1.0"
author = "Dominik Picheta"
description = "Correctly structured package A"
license = "MIT"
# Dependencies
requires "nim >= 0.15.0"

View file

@ -0,0 +1,11 @@
# Package
version = "0.1.0"
author = "Dominik Picheta"
description = "Correctly structured package A"
license = "MIT"
# Dependencies
requires "nim >= 0.15.0", "mydep"

View file

@ -38,6 +38,15 @@ proc execNimble(args: varargs[string]): tuple[output: string, exitCode: int] =
result = execCmdEx(quotedArgs.join(" "))
proc execNimbleYes(args: varargs[string]): tuple[output: string, exitCode: int]=
# issue #6314
execNimble(@args & "-y")
template verify(res: (string, int)) =
let r = res
checkpoint r[0]
check r[1] == QuitSuccess
proc processOutput(output: string): seq[string] =
output.strip.splitLines().filter((x: string) => (x.len > 0))
@ -466,6 +475,29 @@ test "can pass args with spaces to Nim (#351)":
checkpoint output
check exitCode == QuitSuccess
suite "reverse dependencies":
test "basic test":
cd "revdep/mydep":
verify execNimbleYes("install")
cd "revdep/pkgWithDep":
verify execNimbleYes("install")
verify execNimbleYes("remove", "pkgA")
verify execNimbleYes("remove", "mydep")
test "issue #373":
cd "revdep/mydep":
verify execNimbleYes("install")
cd "revdep/pkgWithDep":
verify execNimbleYes("install")
cd "revdep/pkgNoDep":
verify execNimbleYes("install")
verify execNimbleYes("remove", "mydep")
suite "develop feature":
test "can reject binary packages":
cd "develop/binary":