Skip to content

Commit

Permalink
Implement the command upgrade
Browse files Browse the repository at this point in the history
The command can be used to upgrade a minimal set of requirements in a lock file.

Here it is a sample invocation:

```
nimble upgrade foobar@#head
```

I have reworked the way `lock` operation works. Now it uses the lock file and
the applies the requirements from `upgrade`/`requires` section.
  • Loading branch information
yyoncho authored and zah committed Jul 17, 2023
1 parent be01bd4 commit ac4a862
Show file tree
Hide file tree
Showing 7 changed files with 316 additions and 198 deletions.
290 changes: 137 additions & 153 deletions src/nimble.nim

Large diffs are not rendered by default.

9 changes: 6 additions & 3 deletions src/nimblepkg/options.nim
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ type
# For now, it is used only by the run action and it is ignored by others.

ActionType* = enum
actionNil, actionRefresh, actionInit, actionDump, actionPublish,
actionNil, actionRefresh, actionInit, actionDump, actionPublish, actionUpgrade
actionInstall, actionSearch, actionList, actionBuild, actionPath,
actionUninstall, actionCompile, actionDoc, actionCustom, actionTasks,
actionDevelop, actionCheck, actionLock, actionRun, actionSync, actionSetup,
Expand All @@ -73,7 +73,7 @@ type
listOnly*: bool
of actionRefresh:
optionalURL*: string # Overrides default package list.
of actionInstall, actionPath, actionUninstall, actionDevelop:
of actionInstall, actionPath, actionUninstall, actionDevelop, actionUpgrade:
packages*: seq[PkgTuple] # Optional only for actionInstall
# and actionDevelop.
passNimFlags*: seq[string]
Expand Down Expand Up @@ -196,6 +196,7 @@ Commands:
the name of an installed package.
[--ini, --json] Selects the output format (the default is --ini).
lock Generates or updates a package lock file.
upgrade [pkgname, ...] Upgrades a list of packages in the lock file.
deps Outputs dependency tree
[--format type] Specify the output format. Json is the only supported
format
Expand Down Expand Up @@ -303,6 +304,8 @@ proc parseActionType*(action: string): ActionType =
result = actionUninstall
of "publish":
result = actionPublish
of "upgrade":
result = actionUpgrade
of "tasks":
result = actionTasks
of "develop":
Expand Down Expand Up @@ -465,7 +468,7 @@ proc parseArgument*(key: string, result: var Options) =
case result.action.typ
of actionNil:
assert false
of actionInstall, actionPath, actionDevelop, actionUninstall:
of actionInstall, actionPath, actionDevelop, actionUninstall, actionUpgrade:
# Parse pkg@verRange
if '@' in key:
let i = find(key, '@')
Expand Down
4 changes: 2 additions & 2 deletions src/nimblepkg/syncfile.nim
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ type
# Maps develop mode dependency name to the VCS revision it has in the time
# of the last `lock` or `sync` operation or when it is added as a develop
# mode dependency if there is no such operations after that moment.

SyncFile = object
path: Path
data: SyncFileData
Expand All @@ -34,7 +34,7 @@ const
proc getPkgDir(pkgInfo: PackageInfo): string =
pkgInfo.myPath.splitFile.dir

proc getSyncFilePath(pkgInfo: PackageInfo): Path =
proc getSyncFilePath(pkgInfo: PackageInfo): Path =
## Returns a path to the sync file for package `pkgInfo`.

let (vcsType, vcsSpecialDirPath) =
Expand Down
37 changes: 29 additions & 8 deletions src/nimblepkg/topologicalsort.nim
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@
# BSD License. Look at license.txt for more info.

import sequtils, tables, strformat, algorithm, sets
import common, packageinfotypes, packageinfo, options, cli
import common, packageinfotypes, packageinfo, options, cli, version

proc getDependencies(packages: seq[PackageInfo], package: PackageInfo,
proc getDependencies(packages: seq[PackageInfo], requires: seq[PkgTuple],
options: Options):
seq[string] =
## Returns the names of the packages which are dependencies of a given
## package. It is needed because some of the names of the packages in the
## `requires` clause of a package could be URLs.
for dep in package.requires:
for dep in requires:
if dep.name.isNim:
continue
var depPkgInfo = initPackageInfo()
Expand All @@ -20,9 +20,30 @@ proc getDependencies(packages: seq[PackageInfo], package: PackageInfo,
found = findPkg(packages, resolvedDep, depPkgInfo)
if not found:
raise nimbleError(
"Cannot build the dependency graph.\n" &
"Cannot build the dependency graph.\n" &
&"Missing package \"{dep.name}\".")
result.add depPkgInfo.basicInfo.name
result.add depPkgInfo.name

proc allDependencies(requires: seq[PkgTuple], packages: seq[PackageInfo], options: Options): seq[string] =
for dep in requires:
var depPkgInfo = initPackageInfo()
if findPkg(packages, dep, depPkgInfo):
result.add depPkgInfo.name
result.add allDependencies(depPkgInfo.requires, packages, options)
else:
let resolvedDep = dep.resolveAlias(options)
if findPkg(packages, resolvedDep, depPkgInfo):
result.add depPkgInfo.name
result.add allDependencies(depPkgInfo.requires, packages, options)

proc deleteStaleDependencies*(packages: seq[PackageInfo],
rootPackage: PackageInfo,
options: Options): seq[PackageInfo] =
let all = allDependencies(concat(rootPackage.requires,
rootPackage.taskRequires.getOrDefault(options.task)),
packages,
options)
result = packages.filterIt(all.contains(it.name))

proc buildDependencyGraph*(packages: seq[PackageInfo], options: Options):
LockFileDeps =
Expand All @@ -33,7 +54,7 @@ proc buildDependencyGraph*(packages: seq[PackageInfo], options: Options):
vcsRevision: pkgInfo.metaData.vcsRevision,
url: pkgInfo.metaData.url,
downloadMethod: pkgInfo.metaData.downloadMethod,
dependencies: getDependencies(packages, pkgInfo, options),
dependencies: getDependencies(packages, pkgInfo.requires, options),
checksums: Checksums(sha1: pkgInfo.basicInfo.checksum))

proc topologicalSort*(graph: LockFileDeps):
Expand Down Expand Up @@ -77,7 +98,7 @@ proc topologicalSort*(graph: LockFileDeps):

proc printNotADagWarning() =
let message = cycles.foldl(
a & "\nCycle detected: " & b.foldl(&"{a} -> {b}"),
a & "\nCycle detected: " & b.foldl(&"{a} -> {b}"),
"The dependency graph is not a DAG.")
display("Warning", message, Warning, HighPriority)

Expand Down Expand Up @@ -162,7 +183,7 @@ when isMainModule:

expectedTopologicallySortedOrder = @["D", "C", "B", "E", "A"]
expectedCycles = @[@["A", "B", "A"], @["B", "C", "D", "B"], @["E", "E"]]

(actualTopologicallySortedOrder, actualCycles) = topologicalSort(graph)

check actualTopologicallySortedOrder == expectedTopologicallySortedOrder
Expand Down
164 changes: 132 additions & 32 deletions tests/tlockfile.nim
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

{.used.}

import unittest, os, strformat, json, strutils
import unittest, os, strformat, json, strutils, sequtils

import testscommon

Expand Down Expand Up @@ -108,7 +108,7 @@ requires "nim >= 1.5.1"
tryDoCmdEx("git add " & filesStr)

proc commit(msg: string) =
tryDoCmdEx("git commit -m " & msg.quoteShell)
tryDoCmdEx("git commit -am " & msg.quoteShell)

proc push(remote: string) =
tryDoCmdEx(
Expand Down Expand Up @@ -192,7 +192,7 @@ requires "nim >= 1.5.1"
check fileExists(lockFileName)

let (output, exitCode) = if lockFileName == defaultLockFileName:
execNimbleYes("lock", "--debug")
execNimbleYes("lock")
else:
execNimbleYes("--lock-file=" & lockFileName, "lock")

Expand Down Expand Up @@ -228,6 +228,38 @@ requires "nim >= 1.5.1"
usePackageListFile pkgListFilePath:
body

proc getRepoRevision: string =
result = tryDoCmdEx("git rev-parse HEAD").replace("\n", "")

proc getRevision(dep: string, lockFileName = defaultLockFileName): string =
result = lockFileName.readFile.parseJson{$lfjkPackages}{dep}{$lfjkPkgVcsRevision}.str

proc addAdditionalFileAndPushToRemote(
repoPath, remoteName, remotePath, fileContent: string) =
cdNewDir remotePath:
initRepo(isBare = true)
cd repoPath:
# Add commit to the dependency.
addAdditionalFileToTheRepo("dep1.nim", fileContent)
addRemote(remoteName, remotePath)
# Push it to the newly added remote to be able to lock.
push(remoteName)

proc testDepsSync =
let (output, exitCode) = execNimbleYes("sync")
check exitCode == QuitSuccess
let lines = output.processOutput
check lines.inLines(
pkgWorkingCopyIsSyncedMsg(dep1PkgName, dep1PkgRepoPath))
check lines.inLines(
pkgWorkingCopyIsSyncedMsg(dep2PkgName, dep2PkgRepoPath))

cd mainPkgRepoPath:
# After successful sync the revisions written in the lock file must
# match those in the lock file.
testLockedVcsRevisions(@[(dep1PkgName, dep1PkgRepoPath),
(dep2PkgName, dep2PkgRepoPath)])

test "can generate lock file":
cleanUp()
withPkgListFile:
Expand Down Expand Up @@ -384,21 +416,6 @@ requires "nim >= 1.5.1"
check lines.inLines(
pkgWorkingCopyNeedsSyncingMsg(dep2PkgName, dep2PkgRepoPath))

proc testDepsSync =
let (output, exitCode) = execNimbleYes("sync")
check exitCode == QuitSuccess
let lines = output.processOutput
check lines.inLines(
pkgWorkingCopyIsSyncedMsg(dep1PkgName, dep1PkgRepoPath))
check lines.inLines(
pkgWorkingCopyIsSyncedMsg(dep2PkgName, dep2PkgRepoPath))

cd mainPkgRepoPath:
# After successful sync the revisions written in the lock file must
# match those in the lock file.
testLockedVcsRevisions(@[(dep1PkgName, dep1PkgRepoPath),
(dep2PkgName, dep2PkgRepoPath)])

test "can sync out of sync develop dependencies":
outOfSyncDepsTest(""):
testDepsSync()
Expand Down Expand Up @@ -489,17 +506,6 @@ requires "nim >= 1.5.1"
errorMessage = getValidationErrorMessage(dep1PkgName, error)
check output.processOutput.inLines(errorMessage)

proc addAdditionalFileAndPushToRemote(
repoPath, remoteName, remotePath, fileContent: string) =
cdNewDir remotePath:
initRepo(isBare = true)
cd repoPath:
# Add commit to the dependency.
addAdditionalFileToTheRepo("dep1.nim", fileContent)
addRemote(remoteName, remotePath)
# Push it to the newly added remote to be able to lock.
push(remoteName)

test "cannot sync because the working copy needs merge":
cleanUp()
withPkgListFile:
Expand Down Expand Up @@ -591,8 +597,6 @@ requires "nim >= 1.5.1"
writeDevelopFile(developFileName, @[], @[dep1PkgRepoPath, mainPkgOriginRepoPath])
let (_, exitCode) = execNimbleYes("--debug", "--verbose", "sync")
check exitCode == QuitSuccess
proc getRevision(dep: string, lockFileName = defaultLockFileName): string =
result = lockFileName.readFile.parseJson{$lfjkPackages}{dep}{$lfjkPkgVcsRevision}.str

test "can generate lock file for nim as dep":
cleanUp()
Expand All @@ -601,7 +605,7 @@ requires "nim >= 1.5.1"
removeFile "nimble.lock"
removeDir "Nim"

check execNimbleYes("develop", "nim").exitCode == QuitSuccess
check execNimbleYes("-y", "develop", "nim").exitCode == QuitSuccess
cd "Nim":
let (_, exitCode) = execNimbleYes("-y", "install")
check exitCode == QuitSuccess
Expand All @@ -628,3 +632,99 @@ requires "nim >= 1.5.1"
cleanUp()
cd "lockfile-subdep":
check execNimbleYes("test").exitCode == QuitSuccess

test "can upgrade a dependency.":
cleanUp()
withPkgListFile:
initNewNimblePackage(mainPkgOriginRepoPath, mainPkgRepoPath,
@[dep1PkgName])
initNewNimblePackage(dep1PkgOriginRepoPath, dep1PkgRepoPath)

cd mainPkgRepoPath:
check execNimbleYes("lock").exitCode == QuitSuccess

cd dep1PkgOriginRepoPath:
addAdditionalFileToTheRepo("dep1.nim", "echo 42")
let newRevision = getRepoRevision()
cd mainPkgRepoPath:
check newRevision != getRevision(dep1PkgName)
let res = execNimbleYes("upgrade", fmt "{dep1PkgName}@#{newRevision}")
check newRevision == getRevision(dep1PkgName)
check res.exitCode == QuitSuccess

test "can upgrade: the new version of the package has a new dep":
cleanUp()
withPkgListFile:
initNewNimblePackage(mainPkgOriginRepoPath, mainPkgRepoPath, @[dep1PkgName])
initNewNimblePackage(dep1PkgOriginRepoPath, dep1PkgRepoPath)
initNewNimblePackage(dep2PkgOriginRepoPath, dep2PkgRepoPath)

cd mainPkgRepoPath:
check execNimbleYes("lock").exitCode == QuitSuccess

cd dep1PkgOriginRepoPath:
let nimbleFile = initNewNimbleFile(dep1PkgOriginRepoPath, @[dep2PkgName])
addFiles(nimbleFile)
commit("Add dependency to pkg2")

let newRevision = getRepoRevision()
cd mainPkgRepoPath:
let res = execNimbleYes("upgrade", fmt "{dep1PkgName}@#{newRevision}")
check newRevision == getRevision(dep1PkgName)
check res.exitCode == QuitSuccess

testLockedVcsRevisions(@[(dep1PkgName, dep1PkgOriginRepoPath),
(dep2PkgName, dep2PkgOriginRepoPath)])

test "can upgrade: upgrade minimal set of deps":
cleanUp()
withPkgListFile:
initNewNimblePackage(mainPkgOriginRepoPath, mainPkgRepoPath,
@[dep1PkgName])
initNewNimblePackage(dep1PkgOriginRepoPath, dep1PkgRepoPath, @[dep2PkgName])
initNewNimblePackage(dep2PkgOriginRepoPath, dep2PkgRepoPath)

cd mainPkgRepoPath:
check execNimbleYes("lock").exitCode == QuitSuccess

cd dep1PkgOriginRepoPath:
addAdditionalFileToTheRepo("dep1.nim", "echo 1")

cd dep2PkgOriginRepoPath:
let first = getRepoRevision()
addAdditionalFileToTheRepo("dep2.nim", "echo 2")
let second = getRepoRevision()

check execNimbleYes("install").exitCode == QuitSuccess

cd mainPkgRepoPath:
# verify that it won't upgrade version first
check execNimbleYes("upgrade", fmt "{dep1PkgName}@#HEAD").exitCode == QuitSuccess
check getRevision(dep2PkgName) == first

check execNimbleYes("upgrade", fmt "{dep2PkgName}@#{second}").exitCode == QuitSuccess
check getRevision(dep2PkgName) == second

# verify that it won't upgrade version second
check execNimbleYes("upgrade", fmt "{dep1PkgName}@#HEAD").exitCode == QuitSuccess
check getRevision(dep2PkgName) == second

test "can upgrade: the new version of the package with removed dep":
cleanUp()
withPkgListFile:
initNewNimblePackage(mainPkgOriginRepoPath, mainPkgRepoPath, @[dep1PkgName])
initNewNimblePackage(dep1PkgOriginRepoPath, dep1PkgRepoPath, @[dep2PkgName])
initNewNimblePackage(dep2PkgOriginRepoPath, dep2PkgRepoPath)

cd mainPkgRepoPath:
check execNimbleYes("lock").exitCode == QuitSuccess

cd dep1PkgOriginRepoPath:
let nimbleFile = initNewNimbleFile(dep1PkgOriginRepoPath)
addFiles(nimbleFile)
commit("Remove pkg2 dep")

cd mainPkgRepoPath:
let res = execNimbleYes("upgrade", fmt "{dep1PkgName}@#HEAD")
check res.exitCode == QuitSuccess
check defaultLockFileName.readFile.parseJson{$lfjkPackages}.keys.toSeq == @["dep1"]
10 changes: 10 additions & 0 deletions tests/upgradelock/nimdep.nimble
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Package

version = "0.1.0"
author = "Ivan Yonchovski"
description = "A new awesome nimble package"
license = "MIT"
srcDir = "src"
bin = @["demo"]

requires "nim"
Empty file added tests/upgradelock/src/demo.nim
Empty file.

0 comments on commit ac4a862

Please sign in to comment.