1# Copyright (C) Dominik Picheta. All rights reserved.
2# BSD License. Look at license.txt for more info.
3
4import os, json, sets
5
6import options, common, version, download, packageinfo
7
8proc saveNimbleData*(options: Options) =
9  # TODO: This file should probably be locked.
10  writeFile(options.getNimbleDir() / "nimbledata.json",
11            pretty(options.nimbleData))
12
13proc addRevDep*(nimbleData: JsonNode, dep: tuple[name, version: string],
14                pkg: PackageInfo) =
15  # Add a record which specifies that `pkg` has a dependency on `dep`, i.e.
16  # the reverse dependency of `dep` is `pkg`.
17  if not nimbleData["reverseDeps"].hasKey(dep.name):
18    nimbleData["reverseDeps"][dep.name] = newJObject()
19  if not nimbleData["reverseDeps"][dep.name].hasKey(dep.version):
20    nimbleData["reverseDeps"][dep.name][dep.version] = newJArray()
21  let revDep = %{ "name": %pkg.name, "version": %pkg.specialVersion}
22  let thisDep = nimbleData["reverseDeps"][dep.name][dep.version]
23  if revDep notin thisDep:
24    thisDep.add revDep
25
26proc removeRevDep*(nimbleData: JsonNode, pkg: PackageInfo) =
27  ## Removes ``pkg`` from the reverse dependencies of every package.
28  assert(not pkg.isMinimal)
29  proc remove(pkg: PackageInfo, depTup: PkgTuple, thisDep: JsonNode) =
30    for ver, val in thisDep:
31      if ver.newVersion in depTup.ver:
32        var newVal = newJArray()
33        for revDep in val:
34          if not (revDep["name"].str == pkg.name and
35                  revDep["version"].str == pkg.specialVersion):
36            newVal.add revDep
37        thisDep[ver] = newVal
38
39  for depTup in pkg.requires:
40    if depTup.name.isURL():
41      # We sadly must go through everything in this case...
42      for key, val in nimbleData["reverseDeps"]:
43        remove(pkg, depTup, val)
44    else:
45      let thisDep = nimbleData{"reverseDeps", depTup.name}
46      if thisDep.isNil: continue
47      remove(pkg, depTup, thisDep)
48
49  # Clean up empty objects/arrays
50  var newData = newJObject()
51  for key, val in nimbleData["reverseDeps"]:
52    if val.len != 0:
53      var newVal = newJObject()
54      for ver, elem in val:
55        if elem.len != 0:
56          newVal[ver] = elem
57      if newVal.len != 0:
58        newData[key] = newVal
59  nimbleData["reverseDeps"] = newData
60
61proc getRevDepTups*(options: Options, pkg: PackageInfo): seq[PkgTuple] =
62  ## Returns a list of *currently installed* reverse dependencies for `pkg`.
63  result = @[]
64  let thisPkgsDep =
65    options.nimbleData["reverseDeps"]{pkg.name}{pkg.specialVersion}
66  if not thisPkgsDep.isNil:
67    let pkgList = getInstalledPkgsMin(options.getPkgsDir(), options)
68    for pkg in thisPkgsDep:
69      let pkgTup = (
70        name: pkg["name"].getStr(),
71        ver: parseVersionRange(pkg["version"].getStr())
72      )
73      var pkgInfo: PackageInfo
74      if not findPkg(pkgList, pkgTup, pkgInfo):
75        continue
76
77      result.add(pkgTup)
78
79proc getRevDeps*(options: Options, pkg: PackageInfo): HashSet[PackageInfo] =
80  result.init()
81  let installedPkgs = getInstalledPkgsMin(options.getPkgsDir(), options)
82  for rdepTup in getRevDepTups(options, pkg):
83    for rdepInfo in findAllPkgs(installedPkgs, rdepTup):
84      result.incl rdepInfo
85
86proc getAllRevDeps*(options: Options, pkg: PackageInfo, result: var HashSet[PackageInfo]) =
87  if pkg in result:
88    return
89
90  let installedPkgs = getInstalledPkgsMin(options.getPkgsDir(), options)
91  for rdepTup in getRevDepTups(options, pkg):
92    for rdepInfo in findAllPkgs(installedPkgs, rdepTup):
93      if rdepInfo in result:
94        continue
95
96      getAllRevDeps(options, rdepInfo, result)
97  result.incl pkg
98
99when isMainModule:
100  var nimbleData = %{"reverseDeps": newJObject()}
101
102  let nimforum1 = PackageInfo(
103    isMinimal: false,
104    name: "nimforum",
105    specialVersion: "0.1.0",
106    requires: @[("jester", parseVersionRange("0.1.0")),
107                ("captcha", parseVersionRange("1.0.0")),
108                ("auth", parseVersionRange("#head"))]
109  )
110  let nimforum2 = PackageInfo(isMinimal: false, name: "nimforum", specialVersion: "0.2.0")
111  let play = PackageInfo(isMinimal: false, name: "play", specialVersion: "#head")
112
113  nimbleData.addRevDep(("jester", "0.1.0"), nimforum1)
114  nimbleData.addRevDep(("jester", "0.1.0"), play)
115  nimbleData.addRevDep(("captcha", "1.0.0"), nimforum1)
116  nimbleData.addRevDep(("auth", "#head"), nimforum1)
117  nimbleData.addRevDep(("captcha", "1.0.0"), nimforum2)
118  nimbleData.addRevDep(("auth", "#head"), nimforum2)
119
120  doAssert nimbleData["reverseDeps"]["jester"]["0.1.0"].len == 2
121  doAssert nimbleData["reverseDeps"]["captcha"]["1.0.0"].len == 2
122  doAssert nimbleData["reverseDeps"]["auth"]["#head"].len == 2
123
124  block:
125    nimbleData.removeRevDep(nimforum1)
126    let jester = nimbleData["reverseDeps"]["jester"]["0.1.0"][0]
127    doAssert jester["name"].getStr() == play.name
128    doAssert jester["version"].getStr() == play.specialVersion
129
130    let captcha = nimbleData["reverseDeps"]["captcha"]["1.0.0"][0]
131    doAssert captcha["name"].getStr() == nimforum2.name
132    doAssert captcha["version"].getStr() == nimforum2.specialVersion
133
134  echo("Everything works!")
135
136