summary refs log tree commit diff stats
path: root/tools
diff options
context:
space:
mode:
authorAndreas Rumpf <rumpf_a@web.de>2023-05-27 16:48:10 +0200
committerGitHub <noreply@github.com>2023-05-27 16:48:10 +0200
commitef3c0bec1cf02049402a482393581c0a80dd884b (patch)
tree0061d239fb5c78bf06f6ae2b4ad8acd98fa2c820 /tools
parentaf3fd5a010b2f30a007c410858effb3095ef2598 (diff)
downloadNim-ef3c0bec1cf02049402a482393581c0a80dd884b.tar.gz
Atlas: explicit graph representation (#21937)
Diffstat (limited to 'tools')
-rw-r--r--tools/atlas/atlas.nim83
1 files changed, 55 insertions, 28 deletions
diff --git a/tools/atlas/atlas.nim b/tools/atlas/atlas.nim
index e2b86a422..13a2bb785 100644
--- a/tools/atlas/atlas.nim
+++ b/tools/atlas/atlas.nim
@@ -87,13 +87,17 @@ type
     name: PackageName
     url, commit: string
     rel: DepRelation # "requires x < 1.0" is silly, but Nimble allows it so we have too.
+    parents: seq[int] # why we need this dependency
+  DepGraph = object
+    nodes: seq[Dependency]
+    processed: Table[string, int] # the key is (url / commit)
+
   AtlasContext = object
     projectDir, workspace, depsDir: string
     hasPackageList: bool
     keepCommits: bool
     cfgHere: bool
     p: Table[string, string] # name -> url mapping
-    processed: HashSet[string] # the key is (url / commit)
     errors: int
     lockOption: LockOption
     lockFileToWrite: seq[LockFileEntry]
@@ -394,22 +398,32 @@ proc findNimbleFile(c: AtlasContext; dep: Dependency): string =
           # ambiguous .nimble file
           return ""
 
-proc addUniqueDep(c: var AtlasContext; work: var seq[Dependency];
+proc addUnique[T](s: var seq[T]; elem: sink T) =
+  if not s.contains(elem): s.add elem
+
+proc addUniqueDep(c: var AtlasContext; g: var DepGraph; parent: int;
                   tokens: seq[string]; lockfile: Table[string, LockFileEntry]) =
   let pkgName = tokens[0]
   let oldErrors = c.errors
   let url = toUrl(c, pkgName)
   if oldErrors != c.errors:
     warn c, toName(pkgName), "cannot resolve package name"
-  elif not c.processed.containsOrIncl(url / tokens[2]):
-    if lockfile.contains(pkgName):
-      work.add Dependency(name: toName(pkgName),
-                          url: lockfile[pkgName].url,
-                          commit: lockfile[pkgName].commit,
-                          rel: normal)
+  else:
+    let key = url / tokens[2]
+    if g.processed.hasKey(key):
+      g.nodes[g.processed[key]].parents.addUnique parent
     else:
-      work.add Dependency(name: toName(pkgName), url: url, commit: tokens[2],
-                          rel: toDepRelation(tokens[1]))
+      g.processed[key] = g.nodes.len
+      if lockfile.contains(pkgName):
+        g.nodes.add Dependency(name: toName(pkgName),
+                            url: lockfile[pkgName].url,
+                            commit: lockfile[pkgName].commit,
+                            rel: normal,
+                            parents: @[parent])
+      else:
+        g.nodes.add Dependency(name: toName(pkgName), url: url, commit: tokens[2],
+                               rel: toDepRelation(tokens[1]),
+                               parents: @[parent])
 
 template toDestDir(p: PackageName): string = p.string
 
@@ -421,7 +435,7 @@ proc readLockFile(filename: string): Table[string, LockFileEntry] =
   for d in items(data):
     result[d.dir] = d
 
-proc collectDeps(c: var AtlasContext; work: var seq[Dependency];
+proc collectDeps(c: var AtlasContext; g: var DepGraph; parent: int;
                  dep: Dependency; nimbleFile: string): CfgPath =
   # If there is a .nimble file, return the dependency path & srcDir
   # else return "".
@@ -449,25 +463,22 @@ proc collectDeps(c: var AtlasContext; work: var seq[Dependency];
       tokens.add commit
 
     if tokens.len >= 3 and cmpIgnoreCase(tokens[0], "nim") != 0:
-      c.addUniqueDep work, tokens, lockFile
+      c.addUniqueDep g, parent, tokens, lockFile
   result = CfgPath(toDestDir(dep.name) / nimbleInfo.srcDir)
 
-proc collectNewDeps(c: var AtlasContext; work: var seq[Dependency];
-                    dep: Dependency; isMainProject: bool): CfgPath =
+proc collectNewDeps(c: var AtlasContext; g: var DepGraph; parent: int;
+                    dep: Dependency): CfgPath =
   let nimbleFile = findNimbleFile(c, dep)
   if nimbleFile != "":
-    result = collectDeps(c, work, dep, nimbleFile)
+    result = collectDeps(c, g, parent, dep, nimbleFile)
   else:
     result = CfgPath toDestDir(dep.name)
 
-proc addUnique[T](s: var seq[T]; elem: sink T) =
-  if not s.contains(elem): s.add elem
-
-proc traverseLoop(c: var AtlasContext; work: var seq[Dependency]; startIsDep: bool): seq[CfgPath] =
+proc traverseLoop(c: var AtlasContext; g: var DepGraph; startIsDep: bool): seq[CfgPath] =
   result = @[]
   var i = 0
-  while i < work.len:
-    let w = work[i]
+  while i < g.nodes.len:
+    let w = g.nodes[i]
     let destDir = toDestDir(w.name)
     let oldErrors = c.errors
 
@@ -481,22 +492,22 @@ proc traverseLoop(c: var AtlasContext; work: var seq[Dependency]; startIsDep: bo
       # even if the checkout fails, we can make use of the somewhat
       # outdated .nimble file to clone more of the most likely still relevant
       # dependencies:
-      result.addUnique collectNewDeps(c, work, w, i == 0)
+      result.addUnique collectNewDeps(c, g, i, w)
     inc i
 
 proc traverse(c: var AtlasContext; start: string; startIsDep: bool): seq[CfgPath] =
   # returns the list of paths for the nim.cfg file.
   let url = toUrl(c, start)
-  var work = @[Dependency(name: toName(start), url: url, commit: "")]
+  var g = DepGraph(nodes: @[Dependency(name: toName(start), url: url, commit: "")])
 
   if url == "":
     error c, toName(start), "cannot resolve package name"
     return
 
-  c.projectDir = c.workspace / toDestDir(work[0].name)
+  c.projectDir = c.workspace / toDestDir(g.nodes[0].name)
   if c.lockOption == useLock:
     c.lockFileToUse = readLockFile(c.projectDir / LockFileName)
-  result = traverseLoop(c, work, startIsDep)
+  result = traverseLoop(c, g, startIsDep)
   if c.lockOption == genLock:
     writeFile c.projectDir / LockFileName, toJson(c.lockFileToWrite).pretty
 
@@ -553,14 +564,30 @@ proc findSrcDir(c: var AtlasContext): string =
     return nimbleInfo.srcDir
   return ""
 
+proc generateDepGraph(g: DepGraph) =
+  # currently unused.
+  var dotGraph = ""
+  for i in 0 ..< g.nodes.len:
+    for p in items g.nodes[i].parents:
+      if p >= 0:
+        dotGraph.addf("\"$1\" -> \"$2\";\n", [g.nodes[p].name.string, g.nodes[i].name.string])
+  writeFile("deps.dot", "digraph deps {\n$1}\n" % dotGraph)
+  let graphvizDotPath = findExe("dot")
+  if graphvizDotPath.len == 0:
+    #echo("gendepend: Graphviz's tool dot is required, " &
+    #  "see https://graphviz.org/download for downloading")
+    discard
+  else:
+    discard execShellCmd("dot -Tpng -odeps.png deps.dot")
+
 proc installDependencies(c: var AtlasContext; nimbleFile: string) =
   # 1. find .nimble file in CWD
   # 2. install deps from .nimble
-  var work: seq[Dependency] = @[]
+  var g = DepGraph(nodes: @[])
   let (_, pkgname, _) = splitFile(nimbleFile)
   let dep = Dependency(name: toName(pkgname), url: "", commit: "")
-  discard collectDeps(c, work, dep, nimbleFile)
-  let paths = traverseLoop(c, work, startIsDep = true)
+  discard collectDeps(c, g, -1, dep, nimbleFile)
+  let paths = traverseLoop(c, g, startIsDep = true)
   patchNimCfg(c, paths, if c.cfgHere: getCurrentDir() else: findSrcDir(c))
 
 proc updateDir(c: var AtlasContext; dir, filter: string) =