Skip to content

Commit 9fd77e8

Browse files
committed
Fold nimble upgrade into nimble lock --refresh
1 parent 016461e commit 9fd77e8

4 files changed

Lines changed: 185 additions & 29 deletions

File tree

src/nimble.nim

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1490,7 +1490,7 @@ proc lock(options: var Options, nimBin: Option[string]) =
14901490
# upgrading the libraries never moves the compiler. Captured here, BEFORE the
14911491
# lock is rewritten below; written back in the dependency loop.
14921492
var prevNimDep = none(LockFileDep)
1493-
if options.action.typ == actionUpgrade and options.action.packages.len == 0 and lockExists:
1493+
if options.isUpgrade and options.action.packages.len == 0 and lockExists:
14941494
for name, dep in currentLockFile.getLockedDependencies.lockedDepsFor(options):
14951495
if name.isNim:
14961496
prevNimDep = some(dep)
@@ -2450,6 +2450,11 @@ proc doAction(options: var Options, nimBinParam: Option[string]) {.instrument.}
24502450
of actionRun:
24512451
runAction(options, nimBin)
24522452
of actionUpgrade:
2453+
displayWarning("`nimble upgrade` is deprecated and will be removed in a " &
2454+
"future release. Use `nimble lock --refresh` instead.",
2455+
HighPriority)
2456+
# `upgrade` is an alias of `lock --refresh`; `forceFetch` is set at parse
2457+
# time (see parseCommand) so the solver fetches the remotes, not the cache.
24532458
lock(options, nimBin)
24542459
of actionCompile, actionDoc:
24552460
var pkgInfo = getPkgInfo(getCurrentDir(), options, nimBin = nimBin)

src/nimblepkg/nimblesat.nim

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1191,7 +1191,7 @@ proc debugSATResult*(options: Options, calledFrom: string) =
11911191
echo color, "Root requires: ", reset, satResult.rootPackage.requires.mapIt(it.name & " " & $it.ver)
11921192
echo color, "Solved packages: ", reset, satResult.solvedPkgs.mapIt(it.pkgName & " " & $it.version & " " & $it.deps.mapIt(it.pkgName))
11931193
echo color, "Solution as Packages Info: ", reset, satResult.pkgs.mapIt(it.basicInfo.name & " " & $it.basicInfo.version)
1194-
if options.action.typ == actionUpgrade:
1194+
if options.isUpgrade:
11951195
echo color, "Upgrade versions: ", reset, options.action.packages.mapIt(it.name & " " & $it.ver)
11961196
echo color, "RESULT REVISIONS ", reset, satResult.pkgs.mapIt(it.basicInfo.name & " " & $it.metaData.vcsRevision)
11971197
echo color, "PKG LIST REVISIONS ", reset, satResult.pkgList.mapIt(it.basicInfo.name & " " & $it.metaData.vcsRevision)
@@ -1267,7 +1267,7 @@ proc solveLockFileDeps*(satResult: var SATResult, pkgList: seq[PackageInfo], opt
12671267
# in the shouldSolve check. When upgrading, changed requirements for the
12681268
# upgraded packages are expected and should not trigger a full re-solve.
12691269
var upgradePkgNames: seq[string]
1270-
if options.action.typ == actionUpgrade:
1270+
if options.isUpgrade:
12711271
for pkg in options.action.packages:
12721272
upgradePkgNames.add(pkg.name.resolveAlias(options).toLowerAscii())
12731273
for current in currentRequires:
@@ -1294,7 +1294,7 @@ proc solveLockFileDeps*(satResult: var SATResult, pkgList: seq[PackageInfo], opt
12941294
# named packages and keeps the rest locked, which is the wrong model here, so force
12951295
# the full fresh solve. (The install-preferring local solve in solvePackages is
12961296
# already skipped for actionUpgrade, so this resolves to newest.)
1297-
if options.action.typ == actionUpgrade and options.action.packages.len == 0:
1297+
if options.isUpgrade and options.action.packages.len == 0:
12981298
shouldSolve = true
12991299

13001300
var pkgListDecl: seq[PackageInfo]
@@ -1324,7 +1324,7 @@ proc solveLockFileDeps*(satResult: var SATResult, pkgList: seq[PackageInfo], opt
13241324
displayError(satResult.output)
13251325
raise resolutionFailureError(
13261326
"Couldn't find a solution for the packages.")
1327-
elif options.action.typ == actionUpgrade:
1327+
elif options.isUpgrade:
13281328
#[
13291329
Retrocompatibility (goes against SAT in some edge cases)
13301330
When upgrading dep1: Only dep1 should change, dep2 should stay at it is
@@ -1335,24 +1335,36 @@ proc solveLockFileDeps*(satResult: var SATResult, pkgList: seq[PackageInfo], opt
13351335
# Populate requirements from lock file dependencies to preserve them
13361336
let requirements = dep.dependencies.mapIt((name: it, ver: VersionRange(kind: verAny)))
13371337
let solvedPkg = SolvedPackage(pkgName: name, version: dep.version, requirements: requirements)
1338-
if options.action.typ == actionUpgrade:
1338+
if options.isUpgrade:
13391339
if solvedPkg.pkgName in satResult.solvedPkgs.mapIt(it.pkgName):
13401340
satResult.solvedPkgs = satResult.solvedPkgs.filterIt(it.pkgName != name)
13411341
satResult.pkgs = satResult.pkgs.toSeq.filterIt(it.basicInfo.name != name).toHashSet()
1342-
var addedUpgradePkg = false
1342+
var
1343+
addedUpgradePkg = false
1344+
versionComesFromSolve = false
13431345
for upgradePkg in options.action.packages:
13441346
if upgradePkg.name == name:
13451347
if upgradePkg.ver.kind == verSpecial:
13461348
satResult.pkgsToInstall.add((name, upgradePkg.ver.spe))
1347-
# For verAny (no version specified), the temp SAT solve below
1348-
# will determine the correct version to install.
1349+
else:
1350+
# For verAny (no version specified), the temp SAT solve below
1351+
# will determine the correct version to install.
1352+
versionComesFromSolve = true
13491353
addedUpgradePkg = true
13501354
if not addedUpgradePkg:
13511355
for pkg in pkgListDecl.toHashSet():
13521356
if pkg.basicInfo.name == name and pkg.basicInfo.version == dep.version and pkg.metaData.vcsRevision == dep.vcsRevision:
13531357
satResult.pkgs.incl(pkg)
13541358
break
1355-
satResult.solvedPkgs.add(solvedPkg)
1359+
# Carry the locked version over, EXCEPT when the temp solve below is the
1360+
# thing that picks the version. Re-adding it in that case pins the old
1361+
# one: the solve computes the new version, but the merge that follows
1362+
# skips any name already in solvedPkgs, so the stale entry wins and the
1363+
# named package silently never moves. A package pinned to an explicit
1364+
# special version still needs its entry - that path installs from
1365+
# pkgsToInstall above and the solve has nothing to contribute.
1366+
if not versionComesFromSolve:
1367+
satResult.solvedPkgs.add(solvedPkg)
13561368
var pkgListDecl = pkgListDecl
13571369
for upgradePkg in options.action.packages:
13581370
for pkg in pkgList:

src/nimblepkg/options.nim

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -250,8 +250,15 @@ Commands:
250250
[--ini, --json] Selects the output format (the default is --ini). Only applicable to package information.
251251
[--collect] Collects all the packages in the dependency tree of the given package and shows the result in the console.
252252
[--solve] Solves the dependency tree of the given package and shows the result in the console.
253-
lock Generates or updates a package lock file.
254-
upgrade [pkgname, ...] Upgrades a list of packages in the lock file.
253+
lock [pkgname, ...] Generates or updates a package lock file.
254+
Every pin is kept unless the package's
255+
requirements changed. Naming packages relocks
256+
those and leaves the rest of the file pinned.
257+
[--refresh] Resolve against the package repositories
258+
instead of the cached version information, so
259+
newly published versions are picked up. With
260+
no package named, relocks everything.
261+
upgrade [pkgname, ...] Deprecated alias of `lock --refresh`.
255262
deps Outputs dependencies for current package.
256263
[--tree] Outputs dependency tree.
257264
[--inverted] Outputs inverted (reversed) dependency tree.
@@ -484,6 +491,24 @@ proc nim*(options: Options): string =
484491
proc getNimbleDir*(options: Options): string =
485492
return options.nimbleDir
486493

494+
proc isUpgrade*(options: Options): bool =
495+
## Whether this run should relock rather than keep what the lock file pins.
496+
##
497+
## Two things ask `lock` to move a pin
498+
##
499+
## * naming packages - `nimble lock foo` relocks `foo` and leaves the rest of
500+
## the lock file alone.
501+
## * `--refresh` - go look at the remotes first, instead of resolving against
502+
## the tagged versions cache. With no packages named that means relocking
503+
## everything to the newest available.
504+
##
505+
## So `nimble lock foo --refresh` fetches and then relocks just `foo`, and
506+
## plain `nimble lock` keeps every pin. `nimble upgrade` is the deprecated
507+
## spelling of `lock --refresh`.
508+
options.action.typ == actionUpgrade or
509+
(options.action.typ == actionLock and
510+
(options.forceFetch or options.action.packages.len > 0))
511+
487512
proc getPkgsDir*(options: Options): string =
488513
options.getNimbleDir() / nimblePackagesDirName
489514

@@ -645,6 +670,13 @@ proc setNimbleDir*(options: var Options) =
645670
proc parseCommand*(key: string, result: var Options) =
646671
result.action = Action(typ: parseActionType(key))
647672
initAction(result, key)
673+
# `nimble upgrade` is a deprecated alias of `nimble lock --refresh`. Set
674+
# forceFetch here - at parse time, before the SAT solver runs - so version
675+
# discovery goes to the remotes (not the tagged-versions cache) for the whole
676+
# run. Setting it later, in the action dispatch, would be too late: the
677+
# solver would already have resolved dependencies from the cache.
678+
if result.action.typ == actionUpgrade:
679+
result.forceFetch = true
648680

649681

650682
proc getRequiredNimVersion*(pkgInfo: PackageInfo): VersionRange =
@@ -666,7 +698,8 @@ proc parseArgument*(key: string, result: var Options) =
666698
case result.action.typ
667699
of actionNil:
668700
assert false
669-
of actionInstall, actionPath, actionDevelop, actionUninstall, actionUpgrade, actionAdd:
701+
of actionInstall, actionPath, actionDevelop, actionUninstall, actionUpgrade,
702+
actionLock, actionAdd:
670703
# Parse pkg@verRange or git@github.com:nim-lang/nimble.git
671704
# First trim whitespace from the key
672705
let trimmedKey = key.strip()

tests/tnimblerefresh.nim

Lines changed: 122 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import testscommon
99
from nimblepkg/common import cd, cdNewDir
1010
from nimblepkg/tools import tryDoCmdEx
1111
from nimblepkg/packageinfotypes import DownloadMethod
12+
from nimblepkg/options import defaultLockFileName
1213

1314
suite "nimble refresh":
1415
test "can refresh with default urls":
@@ -142,34 +143,41 @@ license = "MIT"
142143
tryDoCmdEx("git add .")
143144
tryDoCmdEx("git commit -am " & msg.quoteShell)
144145

145-
proc writeDepVersion(version: string) =
146-
## Writes dep1.nimble at `version` in the cwd and tags it.
147-
writeFile("dep1.nimble", nimbleFileTemplate % version)
146+
proc writeDepVersion(version: string, name = "dep1") =
147+
## Writes <name>.nimble at `version` in the cwd and tags it.
148+
writeFile(&"{name}.nimble", nimbleFileTemplate % version)
148149
commitAll(version)
149150
tryDoCmdEx(&"git tag v{version}")
150151

151-
proc initDepOrigin(versions: seq[string]) =
152-
cdNewDir depOriginPath:
152+
proc initDepOrigin(versions: seq[string], name = "dep1") =
153+
cdNewDir originsDirPath / name:
153154
initRepo()
154155
for v in versions:
155-
writeDepVersion(v)
156+
writeDepVersion(v, name)
156157

157-
proc addDepVersion(version: string) =
158-
cd depOriginPath:
159-
writeDepVersion(version)
158+
proc addDepVersion(version: string, name = "dep1") =
159+
cd originsDirPath / name:
160+
writeDepVersion(version, name)
160161

161-
proc initMainPkg(requirement: string) =
162+
proc initMainPkg(requirement: string, underVcs = false) =
162163
createDir mainPkgPath
163164
cd mainPkgPath:
164165
writeFile("main.nimble",
165166
(nimbleFileTemplate % "0.1.0") & &"requires \"{requirement}\"\n")
167+
if underVcs:
168+
# Rewriting an existing lock file requires the project dir to be under
169+
# version control (creating one does not).
170+
initRepo()
171+
commitAll("main")
166172

167-
proc writePkgListFile() =
173+
proc writePkgListFile(names = @["dep1"]) =
168174
createDir tempDir
169-
let record = PackagesListFileRecord(
170-
name: "dep1", url: depOriginPath, `method`: DownloadMethod.git,
171-
tags: @["test"], description: "A test package.", license: "MIT")
172-
writeFile(pkgListFilePath, (%(@[record])).pretty)
175+
var records: seq[PackagesListFileRecord]
176+
for name in names:
177+
records.add PackagesListFileRecord(
178+
name: name, url: originsDirPath / name, `method`: DownloadMethod.git,
179+
tags: @["test"], description: "A test package.", license: "MIT")
180+
writeFile(pkgListFilePath, (%records).pretty)
173181

174182
template withCleanDirs(body: untyped) =
175183
removeDir tempDir
@@ -182,15 +190,46 @@ license = "MIT"
182190
template withDepProject(requirement: string, body: untyped) =
183191
## dep1 origin at 0.1.0, a main package requiring it, and a warm cache
184192
## (`nimble install` resolved dep1 once, so tagged_versions.json knows 0.1.0).
193+
withDepProject(requirement, false, body)
194+
195+
template withDepProject(requirement: string, underVcs, body: untyped) =
196+
## As the 2-arg overload, but with the main package's directory under git
197+
## control so the lock file can be rewritten in place.
185198
withCleanDirs:
186199
writePkgListFile()
187200
usePackageListFile pkgListFilePath:
188201
initDepOrigin(@["0.1.0"])
189-
initMainPkg(requirement)
202+
initMainPkg(requirement, underVcs)
203+
cd mainPkgPath:
204+
check execNimbleYes("install").exitCode == QuitSuccess
205+
body
206+
207+
template withTwoDepProject(body: untyped) =
208+
## dep1 and dep2 origins at 0.1.0 and a main package requiring both, under
209+
## git so its lock file can be rewritten. Two deps are what make "only the
210+
## named package moved" observable.
211+
withCleanDirs:
212+
writePkgListFile(@["dep1", "dep2"])
213+
usePackageListFile pkgListFilePath:
214+
initDepOrigin(@["0.1.0"], "dep1")
215+
initDepOrigin(@["0.1.0"], "dep2")
216+
createDir mainPkgPath
190217
cd mainPkgPath:
218+
writeFile("main.nimble", (nimbleFileTemplate % "0.1.0") &
219+
"requires \"dep1 >= 0.1.0\"\nrequires \"dep2 >= 0.1.0\"\n")
220+
initRepo()
221+
commitAll("main")
191222
check execNimbleYes("install").exitCode == QuitSuccess
192223
body
193224

225+
proc lockedVersion(pkg: string): string =
226+
## The version `pkg` is pinned to in the main package's lock file.
227+
let lock = parseJson(readFile(mainPkgPath / defaultLockFileName))
228+
for name, dep in lock["packages"].pairs:
229+
if name.cmpIgnoreCase(pkg) == 0:
230+
return dep["version"].getStr
231+
return ""
232+
194233
test "refresh makes a newly published tag visible":
195234
withDepProject("dep1 >= 0.1.0"):
196235
addDepVersion("0.2.0") # published after the cache was warmed
@@ -300,3 +339,70 @@ license = "MIT"
300339
cd depClonePath:
301340
let tag = tryDoCmdEx("git describe --tags").strip
302341
check tag == "v0.1.0"
342+
343+
test "lock --refresh relocks to a newly published version":
344+
withDepProject("dep1 >= 0.1.0", true):
345+
cd mainPkgPath:
346+
# Lock first so the next publish has an existing pin to keep or move.
347+
check execNimbleYes("lock").exitCode == QuitSuccess
348+
check (defaultLockFileName.readFile).contains("0.1.0")
349+
addDepVersion("0.2.0") # published after the lock was written
350+
cd mainPkgPath:
351+
# A plain `lock` keeps its pins; the newly published version is not used.
352+
check execNimbleYes("lock").exitCode == QuitSuccess
353+
check not (defaultLockFileName.readFile).contains("0.2.0")
354+
check (defaultLockFileName.readFile).contains("0.1.0")
355+
356+
# `lock --refresh` ignores the pins and relocks to the newest.
357+
check execNimbleYes("lock", "--refresh").exitCode == QuitSuccess
358+
check (defaultLockFileName.readFile).contains("0.2.0")
359+
check not (defaultLockFileName.readFile).contains("0.1.0")
360+
361+
test "upgrade still works and says it is deprecated":
362+
withDepProject("dep1 >= 0.1.0", true):
363+
cd mainPkgPath:
364+
# Lock first so `upgrade` relocks an existing pin rather than creating one.
365+
check execNimbleYes("lock").exitCode == QuitSuccess
366+
check (defaultLockFileName.readFile).contains("0.1.0")
367+
addDepVersion("0.2.0") # published after the lock was written
368+
cd mainPkgPath:
369+
let (output, exitCode) = execNimbleYes("upgrade")
370+
check exitCode == QuitSuccess
371+
# `upgrade` is an alias of `lock --refresh`, so it fetches the remotes
372+
# and finds 0.2.0 the same way `lock --refresh` does.
373+
check output.contains("`nimble upgrade` is deprecated")
374+
check output.contains("nimble lock --refresh")
375+
check (defaultLockFileName.readFile).contains("0.2.0")
376+
check not (defaultLockFileName.readFile).contains("0.1.0")
377+
378+
test "lock --refresh pkg relocks only that package":
379+
withTwoDepProject:
380+
cd mainPkgPath:
381+
check execNimbleYes("lock").exitCode == QuitSuccess
382+
check lockedVersion("dep1") == "0.1.0"
383+
check lockedVersion("dep2") == "0.1.0"
384+
addDepVersion("0.2.0", "dep1")
385+
addDepVersion("0.2.0", "dep2")
386+
cd mainPkgPath:
387+
check execNimbleYes("lock", "--refresh", "dep1").exitCode == QuitSuccess
388+
# Naming a package scopes the relock to it; dep2 keeps its pin even
389+
# though 0.2.0 is available for it too.
390+
check lockedVersion("dep1") == "0.2.0"
391+
check lockedVersion("dep2") == "0.1.0"
392+
393+
test "lock pkg relocks from the cache without fetching":
394+
withTwoDepProject:
395+
cd mainPkgPath:
396+
check execNimbleYes("lock").exitCode == QuitSuccess
397+
addDepVersion("0.2.0", "dep1")
398+
cd mainPkgPath:
399+
# Without --refresh nothing is fetched, so the newly published 0.2.0 is
400+
# not visible yet and the pin stays put.
401+
check execNimbleYes("lock", "dep1").exitCode == QuitSuccess
402+
check lockedVersion("dep1") == "0.1.0"
403+
404+
# Once discovery has seen it, naming the package moves it - and only it.
405+
check execNimbleYes("refresh").exitCode == QuitSuccess
406+
check execNimbleYes("lock", "dep1").exitCode == QuitSuccess
407+
check lockedVersion("dep1") == "0.2.0"
408+
check lockedVersion("dep2") == "0.1.0"

0 commit comments

Comments
 (0)