Skip to content

Commit 01161f9

Browse files
isc-jlechtneJames Lechtner
andauthored
Update should check version requirements using post-update values instead of what's currently installed (#1120)
* Update should check version requirements using post-update values instead of what's currently installed * Adding changelog * First round of changes. Describing the addition of the dependency graph * Specifying "the update command" instead of just saying update --------- Co-authored-by: James Lechtner <James.Lechtner@intersystems.com>
1 parent 329ccfd commit 01161f9

18 files changed

Lines changed: 292 additions & 24 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1818
- #1112: Packaging a module with a globals resource now respects SourcesRoot, placing the exported file at the correct path in the tarball
1919
- #1057: Fix IPM not cleaning up after itself on self-uninstall
2020
- #1122: Packaging should recognize resources in dependency modules set to deploy
21+
- #1119: The update command should check version requirements using post-update values instead of what's currently installed
2122

2223
## [0.10.6] - 2026-02-24
2324

src/cls/IPM/Storage/Module.cls

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1109,7 +1109,7 @@ Method ProcessSingleDependencyIterative(
11091109
}
11101110

11111111
set sc = ##class(%IPM.Utils.Module).GetRequiredVersionExpression(
1112-
pDep.Name,otherDepsList,.installedReqExpr,.installedConstraintList
1112+
pDep.Name,otherDepsList,,.installedReqExpr,.installedConstraintList
11131113
)
11141114
$$$ThrowOnError(sc)
11151115
set searchExpr = searchExpr.And(installedReqExpr)

src/cls/IPM/Utils/Module.cls

Lines changed: 66 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ ClassMethod LoadModuleReference(
114114

115115
// Ensure requested versions match those required by other modules in the namespace, excluding versions currently being installed
116116
// (the requirements of such modules are already known to be satisfied)
117-
set tSC = ..GetRequiredVersionExpression(pModuleName,,.tExpression,.tSourceList)
117+
set tSC = ..GetRequiredVersionExpression(pModuleName,,.pDependencyGraph,.tExpression,.tSourceList)
118118
if $$$ISERR(tSC) {
119119
quit
120120
}
@@ -349,36 +349,79 @@ ClassMethod GetModuleObjectFromString(
349349

350350
/// Returns a semantic version expression capturing all version requirements for a given module name in the current namespace.
351351
/// A list of modules to exclude may be provided (for example, if these modules would be updated at the same time).
352+
///
353+
/// If provided a dependency graph, will use versions defined there instead of what the SQL call returns.
354+
/// This is especially important for the update command, where we want to check requirements with post-update versions as opposed to what's currently installed.
352355
ClassMethod GetRequiredVersionExpression(
353-
pModuleName As %String,
354-
pExcludeModules As %List = "",
355-
Output pExpression As %IPM.General.SemanticVersionExpression,
356-
Output pSourceList As %List) As %Status
356+
moduleName As %String,
357+
excludeModules As %List = "",
358+
ByRef dependencyGraph,
359+
Output expression As %IPM.General.SemanticVersionExpression,
360+
Output sourceList As %List) As %Status
357361
{
358-
set tSC = $$$OK
362+
set sc = $$$OK
359363
try {
360-
set pExpression = ##class(%IPM.General.SemanticVersionExpression).%New()
361-
set pSourceList = ""
364+
// Add modules from the dependency graph to the excludeModules list
365+
if ($data(dependencyGraph)) {
366+
do ..ConstructInvertedDependencyGraph(.dependencyGraph, .invertedDependencyGraph)
367+
set flatDepList = ..GetFlatDependencyListFromInvertedDependencyGraph(.invertedDependencyGraph)
368+
for i = 1:1:flatDepList.Count() {
369+
set excludeModules = excludeModules _ $listbuild(flatDepList.GetAt(i).Name)
370+
}
371+
}
362372

363-
set tResult = ##class(%IPM.Storage.Module).VersionRequirementsFunc(pModuleName,pExcludeModules)
364-
if (tResult.%SQLCODE < 0) {
365-
$$$ThrowStatus($$$ERROR($$$SQLCode,tResult.%SQLCODE,tResult.%Message))
373+
set expression = ##class(%IPM.General.SemanticVersionExpression).%New()
374+
set sourceList = ""
375+
376+
set result = ##class(%IPM.Storage.Module).VersionRequirementsFunc(moduleName,excludeModules)
377+
if (result.%SQLCODE < 0) {
378+
$$$ThrowStatus($$$ERROR($$$SQLCode,result.%SQLCODE,result.%Message))
366379
}
367380

368-
while tResult.%Next(.tSC) {
369-
$$$ThrowOnError(tSC)
370-
set tVersion = tResult.%Get("Version")
371-
$$$ThrowOnError(##class(%IPM.General.SemanticVersionExpression).FromString(tVersion,.tVersionExpr))
372-
set pExpression = pExpression.And(tVersionExpr)
373-
set pSourceList = pSourceList_$listbuild($listtostring(tResult.%Get("ModuleNames"),", ")_": "_tVersion)
381+
while result.%Next(.sc) {
382+
$$$ThrowOnError(sc)
383+
set version = result.%Get("Version")
384+
$$$ThrowOnError(##class(%IPM.General.SemanticVersionExpression).FromString(version,.versionExpr))
385+
set expression = expression.And(versionExpr)
386+
set sourceList = sourceList_$listbuild($listtostring(result.%Get("ModuleNames"),", ")_": "_version)
387+
}
388+
$$$ThrowOnError(sc)
389+
390+
// Add modules from the dependency graph to the expression and sourceList objects
391+
if ($data(dependencyGraph)) {
392+
set key = ""
393+
for {
394+
set key = $order(dependencyGraph(moduleName, key))
395+
if (key = "") {
396+
quit
397+
}
398+
// Name of module which requires this one + the version expression string it requires
399+
set requiringModuleName = $piece(key, " ")
400+
set version = dependencyGraph(moduleName, key)
401+
402+
// Iterate over sourceList; if the required version expression is equivalent to this one, add this module name to that version
403+
set newVersion = 1
404+
for i=1:1:$listlength(sourceList) {
405+
if $find($listget(sourceList, i), version) {
406+
set $list(sourceList, i) = requiringModuleName _ ", " _ $list(sourceList, i)
407+
set newVersion = 0
408+
quit
409+
}
410+
}
411+
// If this is a new required version expression, add a new item to the list
412+
if (newVersion) {
413+
$$$ThrowOnError(##class(%IPM.General.SemanticVersionExpression).FromString(version, .versionExpr))
414+
set expression = expression.And(versionExpr)
415+
set sourceList = sourceList _ $listbuild(requiringModuleName _ ": " _ version)
416+
}
417+
}
374418
}
375-
$$$ThrowOnError(tSC)
376419
} catch e {
377-
set pExpression = $$$NULLOREF
378-
set pSourceList = ""
379-
set tSC = e.AsStatus()
420+
set expression = $$$NULLOREF
421+
set sourceList = ""
422+
set sc = e.AsStatus()
380423
}
381-
quit tSC
424+
quit sc
382425
}
383426

384427
/// Returns a flat list of dependents for a given module name (and optional version) <br />
@@ -1278,7 +1321,7 @@ ClassMethod LoadDependencies(
12781321
// Ignore modules already installed that do not need to be installed again
12791322
continue
12801323
}
1281-
set sc = ..LoadModuleReference(moduleReference.ServerName, moduleReference.Name, moduleReference.VersionString, moduleReference.Deployed, moduleReference.PlatformVersion, .pParams)
1324+
set sc = ..LoadModuleReference(moduleReference.ServerName, moduleReference.Name, moduleReference.VersionString, moduleReference.Deployed, moduleReference.PlatformVersion, .pParams, .dependencyGraph)
12821325
$$$ThrowOnError(sc)
12831326
}
12841327

tests/integration_tests/Test/PM/Integration/Update.cls

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -662,4 +662,65 @@ Method TestDevModePropagation()
662662
do $$$AssertEquals(dep.DeveloperMode, 0)
663663
}
664664

665+
/// Test case to make sure that update doesn't fail due to version errors that get resolved as a part of update
666+
/// i.e. - Module A 2.0.0 requires Module B ^1.0.0
667+
/// - Module A 2.1.0 requires Module B ^2.0.0
668+
/// - Pre-Update: Module A 2.0.0, Module B 1.0.0
669+
/// - Post-Update: Module A 2.1.0, Module B 2.0.0
670+
/// - Don't want a version error to be thrown in flight when Module A is 2.1.0 and Module B is still 1.0.0
671+
Method TestDependencyVersionsUpdated()
672+
{
673+
// Define variables for this test
674+
set modA = "module-a"
675+
set modB = "module-b"
676+
set modC = "module-c"
677+
set modD = "module-d"
678+
679+
set modAPreVersion = "2.0.0"
680+
set modBPreVersion = "1.0.0"
681+
set modCPreVersion = "5.6.45+snapshot"
682+
683+
set modAPostVersion = "2.1.0+snapshot"
684+
set modBPostVersion = "2.0.0+snapshot"
685+
set modCPostVersion = "6.2.0+snapshot"
686+
687+
// Install base module module-a
688+
set sc = ##class(%IPM.Main).Shell("install -v " _ modA _ " " _ modAPreVersion)
689+
do $$$AssertStatusOK(sc, "Successfully installed " _ modA _ " " _ modAPreVersion)
690+
691+
// Confirm that module-a and dependencies were installed with the correct versions
692+
set mod = ##class(%IPM.Storage.Module).NameOpen(modA)
693+
do $$$AssertEquals(mod.Version.ToString(), modAPreVersion, "Module " _ modA _ " correctly installed as " _ modAPreVersion)
694+
set mod = ##class(%IPM.Storage.Module).NameOpen(modB)
695+
do $$$AssertEquals(mod.Version.ToString(), modBPreVersion, "Module " _ modB _ " correctly installed as " _ modBPreVersion)
696+
set mod = ##class(%IPM.Storage.Module).NameOpen(modC)
697+
do $$$AssertEquals(mod.Version.ToString(), modCPreVersion, "Module " _ modC _ " correctly installed as " _ modCPreVersion)
698+
699+
// Install module-d prior to update
700+
set sc = ##class(%IPM.Main).Shell("install -v " _ modD)
701+
do $$$AssertStatusOK(sc, "Successfully installed " _ modD)
702+
703+
// Update base module module-a. Update should error due to version incompatabilities caused by module-d
704+
set sc = ##class(%IPM.Main).Shell("update -v " _ modA _ " " _ modAPostVersion)
705+
do $$$AssertStatusNotOK(sc, "Updating " _ modA _ " fails due to version errors with previously installed " _ modD)
706+
707+
// Uninstall module-d then try the update again
708+
set sc = ##class(%IPM.Main).Shell("uninstall -v " _ modD)
709+
do $$$AssertStatusOK(sc, "Successfully uninstalled " _ modD)
710+
set sc = ##class(%IPM.Main).Shell("update -v " _ modA _ " " _ modAPostVersion)
711+
do $$$AssertStatusOK(sc, "Successfully updated " _ modA _ " and its dependencies")
712+
713+
// Confirm that module-a and dependencies were updated to the correct versions
714+
set mod = ##class(%IPM.Storage.Module).NameOpen(modA)
715+
do $$$AssertEquals(mod.Version.ToString(), modAPostVersion, "Module " _ modA _ " correctly installed as " _ modAPostVersion)
716+
set mod = ##class(%IPM.Storage.Module).NameOpen(modB)
717+
do $$$AssertEquals(mod.Version.ToString(), modBPostVersion, "Module " _ modB _ " correctly installed as " _ modBPostVersion)
718+
set mod = ##class(%IPM.Storage.Module).NameOpen(modC)
719+
do $$$AssertEquals(mod.Version.ToString(), modCPostVersion, "Module " _ modC _ " correctly installed as " _ modCPostVersion)
720+
721+
// Uninstall module-a and dependencies
722+
set sc = ##class(%IPM.Main).Shell("uninstall -r " _ modA)
723+
do $$$AssertStatusOK(sc, "Successfully uninstalled " _ modA _ " and dependencies")
724+
}
725+
665726
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
Class ModuleA.Class1
2+
{
3+
4+
ClassMethod MethodA()
5+
{
6+
write !, "This is ##class(ModuleA.Class1).MethodA()"
7+
}
8+
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<Export generator="Cache" version="25">
3+
<Document name="module-a.ZPM">
4+
<Module>
5+
<Name>module-a</Name>
6+
<Version>2.1.0+snapshot</Version>
7+
<Resource Name="ModuleA.PKG" />
8+
<Dependencies>
9+
<ModuleReference>
10+
<Name>module-b</Name>
11+
<Version>^2.0.0</Version>
12+
</ModuleReference>
13+
</Dependencies>
14+
</Module>
15+
</Document>
16+
</Export>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
Class ModuleB.Class1
2+
{
3+
4+
ClassMethod MethodA()
5+
{
6+
write !, "This is ##class(ModuleB.Class1).MethodA()"
7+
}
8+
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<Export generator="Cache" version="25">
3+
<Document name="module-b.ZPM">
4+
<Module>
5+
<Name>module-b</Name>
6+
<Version>2.0.0+snapshot</Version>
7+
<Resource Name="ModuleB.PKG" />
8+
<Dependencies>
9+
<ModuleReference>
10+
<Name>module-c</Name>
11+
<Version>^6.1.15</Version>
12+
</ModuleReference>
13+
</Dependencies>
14+
</Module>
15+
</Document>
16+
</Export>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
Class ModuleC.Class1
2+
{
3+
4+
ClassMethod MethodA()
5+
{
6+
write !, "This is ##class(ModuleC.Class1).MethodA()"
7+
}
8+
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<Export generator="Cache" version="25">
3+
<Document name="module-c.ZPM">
4+
<Module>
5+
<Name>module-c</Name>
6+
<Version>6.2.0+snapshot</Version>
7+
<Resource Name="ModuleC.PKG" />
8+
</Module>
9+
</Document>
10+
</Export>

0 commit comments

Comments
 (0)