parent
3c1e669eaa
commit
92e9ec5e59
6 changed files with 203 additions and 74 deletions
|
|
@ -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:
|
||||
let revDeps = getRevDeps(options, pkg)
|
||||
var reason = ""
|
||||
if thisPkgsDep.len == 1:
|
||||
reason = "$1 ($2) depends on it" % [thisPkgsDep[0]["name"].str,
|
||||
thisPkgsDep[0]["version"].str]
|
||||
if revDeps.len == 1:
|
||||
reason = "$1 ($2) depends on it" % [revDeps[0].name, $revDeps[0].ver]
|
||||
else:
|
||||
for i in 0 .. <thisPkgsDep.len:
|
||||
reason.add("$1 ($2)" % [thisPkgsDep[i]["name"].str,
|
||||
thisPkgsDep[i]["version"].str])
|
||||
if i != <thisPkgsDep.len:
|
||||
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"
|
||||
errors.add("Cannot uninstall $1 ($2) because $3" % [pkgTup.name,
|
||||
pkg.specialVersion, reason])
|
||||
|
||||
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)
|
||||
|
|
|
|||
115
src/nimblepkg/reversedeps.nim
Normal file
115
src/nimblepkg/reversedeps.nim
Normal 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!")
|
||||
|
||||
10
tests/revdep/mydep/mydep.nimble
Normal file
10
tests/revdep/mydep/mydep.nimble
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# Package
|
||||
|
||||
version = "0.1.0"
|
||||
author = "Dominik Picheta"
|
||||
description = "Random dep"
|
||||
license = "MIT"
|
||||
|
||||
# Dependencies
|
||||
|
||||
requires "nim >= 0.15.0"
|
||||
11
tests/revdep/pkgNoDep/pkgA.nimble
Normal file
11
tests/revdep/pkgNoDep/pkgA.nimble
Normal 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"
|
||||
|
||||
11
tests/revdep/pkgWithDep/pkgA.nimble
Normal file
11
tests/revdep/pkgWithDep/pkgA.nimble
Normal 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"
|
||||
|
||||
|
|
@ -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":
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue