summary refs log tree commit diff stats
path: root/tools
diff options
context:
space:
mode:
authorAndreas Rumpf <rumpf_a@web.de>2023-05-23 23:48:00 +0200
committerGitHub <noreply@github.com>2023-05-23 23:48:00 +0200
commit9493e6729138ecfdbc3f6ad433a32fcf19467a34 (patch)
tree18d93e550716bd5463bdd0311e952095fbe13b25 /tools
parentbdccc9fef93d6598ec453a19152c4e98539f783f (diff)
downloadNim-9493e6729138ecfdbc3f6ad433a32fcf19467a34.tar.gz
Atlas: first lockfiles implementation; cleared up upated vs updateWor… (#21895)
Atlas: first lockfiles implementation; cleared up upated vs updateWorkspace commands
Diffstat (limited to 'tools')
-rw-r--r--tools/atlas/atlas.md10
-rw-r--r--tools/atlas/atlas.nim84
2 files changed, 81 insertions, 13 deletions
diff --git a/tools/atlas/atlas.md b/tools/atlas/atlas.md
index cbdb54b9a..3e9bc32e5 100644
--- a/tools/atlas/atlas.md
+++ b/tools/atlas/atlas.md
@@ -60,17 +60,21 @@ patch the `nim.cfg` then.
 Atlas supports the following commands:
 
 
-### Clone <url>
+### Clone/Update <url>
 
 Clones a URL and all of its dependencies (recursively) into the workspace.
 Creates or patches a `nim.cfg` file with the required `--path` entries.
 
+**Note**: Due to the used algorithms an `update` is the same as a `clone`.
 
-### Clone <package name>
+
+### Clone/Update <package name>
 
 The `<package name>` is translated into an URL via `packages.json` and
 then `clone <url>` is performed.
 
+**Note**: Due to the used algorithms an `update` is the same as a `clone`.
+
 
 ### Search <term term2 term3 ...>
 
@@ -82,7 +86,7 @@ in its description (or name or list of tags).
 
 Use the .nimble file to setup the project's dependencies.
 
-### Update [filter]
+### UpdateWorkspace [filter]
 
 Update every package in the workspace that has a remote URL that
 matches `filter` if a filter is given. The package is only updated
diff --git a/tools/atlas/atlas.nim b/tools/atlas/atlas.nim
index 4662edf9d..0358e107c 100644
--- a/tools/atlas/atlas.nim
+++ b/tools/atlas/atlas.nim
@@ -9,13 +9,14 @@
 ## Simple tool to automate frequent workflows: Can "clone"
 ## a Nimble dependency and its dependencies recursively.
 
-import std/[parseopt, strutils, os, osproc, tables, sets, json, jsonutils]
+import std / [parseopt, strutils, os, osproc, tables, sets, json, jsonutils]
 import parse_requires, osutils, packagesjson
 
 from unicode import nil
 
 const
-  Version = "0.2"
+  Version = "0.3"
+  LockFileName = "atlas.lock"
   Usage = "atlas - Nim Package Cloner Version " & Version & """
 
   (c) 2021 Andreas Rumpf
@@ -23,11 +24,13 @@ Usage:
   atlas [options] [command] [arguments]
 Command:
   clone url|pkgname     clone a package and all of its dependencies
+  update url|pkgname    update a package and all of its dependencies
   install proj.nimble   use the .nimble file to setup the project's dependencies
   search keyw keywB...  search for package that contains the given keywords
   extract file.nimble   extract the requirements and custom commands from
                         the given Nimble file
-  update [filter]       update every package in the workspace that has a remote
+  updateWorkspace [filter]
+                        update every package in the workspace that has a remote
                         URL that matches `filter` if a filter is given
   build|test|doc|tasks  currently delegates to `nimble build|test|doc`
   task <taskname>       currently delegates to `nimble <taskname>`
@@ -40,6 +43,8 @@ Options:
   --deps=DIR            store dependencies in DIR instead of the workspace
                         (if DIR is a relative path, it is interpreted to
                         be relative to the workspace)
+  --genlock             generate a lock file (use with `clone` and `update`)
+  --uselock             use the lock file for the build
   --version             show the version
   --help                show this help
 """
@@ -59,6 +64,12 @@ const
   TestsDir = "tools/atlas/tests"
 
 type
+  LockOption = enum
+    noLock, genLock, useLock
+
+  LockFileEntry = object
+    dir, url, commit: string
+
   PackageName = distinct string
   DepRelation = enum
     normal, strictlyLess, strictlyGreater
@@ -75,6 +86,9 @@ type
     p: Table[string, string] # name -> url mapping
     processed: HashSet[string] # the key is (url / commit)
     errors: int
+    lockOption: LockOption
+    lockFileToWrite: seq[LockFileEntry]
+    lockFileToUse: Table[string, LockFileEntry]
     when MockupRun:
       currentDir: string
       step: int
@@ -279,23 +293,51 @@ proc needsCommitLookup(commit: string): bool {.inline.} =
 proc isShortCommitHash(commit: string): bool {.inline.} =
   commit.len >= 4 and commit.len < 40
 
+proc getRequiredCommit(c: var AtlasContext; w: Dependency): string =
+  if needsCommitLookup(w.commit): versionToCommit(c, w)
+  elif isShortCommitHash(w.commit): shortToCommit(c, w.commit)
+  else: w.commit
+
+proc getRemoteUrl(): string =
+  execProcess("git config --get remote.origin.url").strip()
+
+proc genLockEntry(c: var AtlasContext; w: Dependency; dir: string) =
+  let url = getRemoteUrl()
+  var commit = getRequiredCommit(c, w)
+  if commit.len == 0 or needsCommitLookup(commit):
+    commit = execProcess("git log -1 --pretty=format:%H").strip()
+  c.lockFileToWrite.add LockFileEntry(dir: relativePath(dir, c.workspace, '/'), url: url, commit: commit)
+
+proc commitFromLockFile(c: var AtlasContext; dir: string): string =
+  let url = getRemoteUrl()
+  let d = relativePath(dir, c.workspace, '/')
+  if d in c.lockFileToUse:
+    result = c.lockFileToUse[d].commit
+    let wanted = c.lockFileToUse[d].url
+    if wanted != url:
+      error c, PackageName(d), "remote URL has been compromised: got: " &
+          url & " but wanted: " & wanted
+  else:
+    error c, PackageName(d), "package is not listed in the lock file"
+
 proc checkoutCommit(c: var AtlasContext; w: Dependency) =
   var dir = c.workspace / w.name.string
   if not dirExists(dir):
     dir = c.depsDir / w.name.string
 
   withDir c, dir:
-    if w.commit.len == 0 or cmpIgnoreCase(w.commit, "head") == 0:
+    if c.lockOption == genLock:
+      genLockEntry(c, w, dir)
+    elif c.lockOption == useLock:
+      checkoutGitCommit(c, w.name, commitFromLockFile(c, dir))
+    elif w.commit.len == 0 or cmpIgnoreCase(w.commit, "head") == 0:
       gitPull(c, w.name)
     else:
       let err = isCleanGit(c)
       if err != "":
         warn c, w.name, err
       else:
-        let requiredCommit =
-          if needsCommitLookup(w.commit): versionToCommit(c, w)
-          elif isShortCommitHash(w.commit): shortToCommit(c, w.commit)
-          else: w.commit
+        let requiredCommit = getRequiredCommit(c, w)
         let (cc, status) = exec(c, GitCurrentCommit, [])
         let currentCommit = strutils.strip(cc)
         if requiredCommit == "" or status != 0:
@@ -403,6 +445,14 @@ proc cloneLoop(c: var AtlasContext; work: var seq[Dependency]): seq[string] =
       collectNewDeps(c, work, w, result, i == 0)
     inc i
 
+proc readLockFile(c: var AtlasContext) =
+  let jsonAsStr = readFile(c.projectDir / LockFileName)
+  let jsonTree = parseJson(jsonAsStr)
+  let data = to(jsonTree, seq[LockFileEntry])
+  c.lockFileToUse = initTable[string, LockFileEntry]()
+  for d in items(data):
+    c.lockFileToUse[d.dir] = d
+
 proc clone(c: var AtlasContext; start: string): seq[string] =
   # non-recursive clone.
   let url = toUrl(c, start)
@@ -413,7 +463,11 @@ proc clone(c: var AtlasContext; start: string): seq[string] =
     return
 
   c.projectDir = c.workspace / toDestDir(work[0].name)
+  if c.lockOption == useLock:
+    readLockFile c
   result = cloneLoop(c, work)
+  if c.lockOption == genLock:
+    writeFile c.projectDir / LockFileName, toJson(c.lockFileToWrite).pretty
 
 const
   configPatternBegin = "############# begin Atlas config section ##########\n"
@@ -534,6 +588,16 @@ proc main =
         else:
           writeHelp()
       of "cfghere": c.cfgHere = true
+      of "genlock":
+        if c.lockOption != useLock:
+          c.lockOption = genLock
+        else:
+          writeHelp()
+      of "uselock":
+        if c.lockOption != genLock:
+          c.lockOption = useLock
+        else:
+          writeHelp()
       else: writeHelp()
     of cmdEnd: assert false, "cannot happen"
 
@@ -561,7 +625,7 @@ proc main =
   case action
   of "":
     error "No action."
-  of "clone":
+  of "clone", "update":
     singleArg()
     let deps = clone(c, args[0])
     patchNimCfg c, deps, if c.cfgHere: getCurrentDir() else: findSrcDir(c)
@@ -590,7 +654,7 @@ proc main =
   of "search", "list":
     updatePackages(c)
     search getPackages(c.workspace), args
-  of "update":
+  of "updateworkspace":
     updateWorkspace(c, c.workspace, if args.len == 0: "" else: args[0])
     updateWorkspace(c, c.depsDir, if args.len == 0: "" else: args[0])
   of "extract":