feat(lifecycle): add automatic orphaned module cleanup to update command#1059
feat(lifecycle): add automatic orphaned module cleanup to update command#1059AshokThangavel wants to merge 11 commits intointersystems:mainfrom
Conversation
- Implement logic to identify and uninstall dependencies removed in new module versions. - Introduce -keep-orphans (-ko) flag to allow users to opt-out of cleanup. - Ensure cleanup logic is scoped strictly to the 'update' command. - Add integration tests in Test.PM.Unit.OrphanCleanup to verify flag behavior. - Update command metadata and help text for 'update'.
There was a problem hiding this comment.
@AshokThangavel functionality looks good! I left some comments. Mostly nits!
Also I think the CHANGELOG will need to be updated due to IPM version having changed in the interim
| @@ -0,0 +1,229 @@ | |||
| Class Test.PM.Integration.OrphanCleanup Extends Test.PM.Integration.Base | |||
There was a problem hiding this comment.
nit: We already have an existing Update test suite in Test.PM.Integration.Update. I suggest either adding to that suite, or naming this test suite to be explicitly update related (eg. UpdateOrphanCleanup.cls)
There was a problem hiding this comment.
Thank you. I've updated the class name to Test.PM.Integration.UpdateOrphanCleanup
src/cls/IPM/Main.cls
Outdated
| <modifier name="path" aliases="p" value="true" description="Location of local tarball containing the updated version of the module. Overrides 'version' parameter if present." /> | ||
| <modifier name="dev" dataAlias="DeveloperMode" dataValue="1" description="Sets the DeveloperMode flag for the module's lifecycle. Key consequences of this are that ^Sources will be configured for resources in the module, and installer methods will be called with the dev mode flag set." /> | ||
| <modifier name="create-lockfile" aliases="lock" dataAlias="CreateLockFile" dataValue="1" description="Upon update, creates/updates the module's lock file." /> | ||
| <modifier name="keep-orphans" aliases="ko" dataAlias="KeepOrphans" dataValue="1" description="Retains dependencies that are no longer required by the updated module. By default, these orphaned modules are automatically uninstalled." /> |
There was a problem hiding this comment.
nit: I don't love the alias "ko" as it has "kill" implications which is the opposite of what this does (ie. "k" by itself in Objectscript means kill, "KO" means kill in other contexts) so that could be misleading. I suggest either naming keep-orphans differently, or abbreviating it differently
There was a problem hiding this comment.
thank you. I agree, I've modified the alias to -ro (Retain Orphans) .
There was a problem hiding this comment.
Hi @AshokThangavel thank you for the change! I discussed with my team and we believe the best way is actually to remove the alias entirely. Since "ro" also has the more widely known meaning "read-only". It's okay for this command to not have an alias.
There was a problem hiding this comment.
Hi @isc-jili, thanks for confirming. I’ve removed the -ro alias for keep-orphans, and as suggested, there is now no alias for it. Thank you!
There was a problem hiding this comment.
updated the pull request description as well.
| set depsCount = moduleObj.Dependencies.Count() | ||
| for i=1:1:depsCount{ | ||
| set depsName = moduleObj.Dependencies.GetAt(i) | ||
| do $$$LogMessage("Module "_depsName.Name_" installed successfully") |
There was a problem hiding this comment.
nit: in the log make it explicit that these installed modules are dependencies of module1
There was a problem hiding this comment.
updated the code!
| do $$$LogMessage("Module "_depsName.Name_" installed successfully") | ||
| } | ||
| if depsCount = 3 { | ||
| do $$$LogMessage("All 3 modules are loaded successfully") |
There was a problem hiding this comment.
nit: "All 3 dependency modules ..."
There was a problem hiding this comment.
Fixed the message. Thank you!
|
|
||
| for i=1:1:depsCount{ | ||
| set depsName = moduleObj.Dependencies.GetAt(i) | ||
| do $$$LogMessage("Module "_depsName.Name_" exist") |
There was a problem hiding this comment.
nit: "Dependency module ..."
There was a problem hiding this comment.
Fixed the message.
| { | ||
| if '##class(%File).DirectoryExists(Directory) { | ||
| set status = ##class(%File).CreateDirectoryChain(Directory) | ||
| do $$$AssertStatusOK(status," Directory"_Directory_" not exist. So created the directory") |
There was a problem hiding this comment.
nit: "Directory "Directory" does not exist. So created the directory."
There was a problem hiding this comment.
modified the code.
| do $$$AssertStatusOK(status," Directory"_Directory_" not exist. So created the directory") | ||
| } | ||
| else { | ||
| do $$$LogMessage("Directory "_Directory_" is already exist") |
There was a problem hiding this comment.
nit: "Directory "Directory" already exists."
There was a problem hiding this comment.
fixed. Thank you!
|
|
||
| Method CreateModuleFile( | ||
| ModuleFile As %String = "", | ||
| moduleXdata As %String = "") |
There was a problem hiding this comment.
nit: Inconsistent capitalization of variables
There was a problem hiding this comment.
Corrected the Parameter variables Inconsistency. Thank you!
| quit 0 | ||
| } | ||
| if '##class(%File).Exists(ModuleFile) { | ||
| do $$$LogMessage(ModuleFile_" not exist. So creating the xml") |
There was a problem hiding this comment.
added the changes!
src/cls/IPM/Utils/Module.cls
Outdated
| Output OrphanedModules) | ||
| { | ||
| kill OrphanedModules | ||
| merge existDeps = ExistDependencies |
There was a problem hiding this comment.
nit: inconsistent casing for vars
Also, what is the point of making a copy of ExistDependencies instead of acting directly on ExistDependencies? I don't see ExistDependencies being used after this method call.
There was a problem hiding this comment.
I've updated the code.
Thank you!
* Added 'ro' as a short alias for the '-keep-orphans' flag. * Renamed 'OrphanCleanup' class to 'UpdateOrphanCleanup' for better architectural clarity.
isc-jili
left a comment
There was a problem hiding this comment.
@AshokThangavel it's getting really close! Left another round mostly small suggestions. Thanks for implementing this feature!
| /// Recursively removes modules not required by the main module or other local packages. | ||
| ClassMethod UninstallOrphanedDependencies( | ||
| ByRef orphanedDeps, | ||
| ByRef Params) |
There was a problem hiding this comment.
nit: inconsistent capitalization of variables.
I suggest turning on the force-formatting rules found in IPM/.vscode/settings.json if working with VSCode.
There was a problem hiding this comment.
Fixed the capitalization of variables. Thank you!
These are the existing Force-formatting rules in the settings.json. Please let me know if anything missing
// Force formatting rules
"intersystems.testingManager.client.relativeTestRoot": "tests/unit_tests",
"objectscript.multilineMethodArgs": true,
"intersystems.language-server.formatting.expandClassNames": false,
"intersystems.language-server.formatting.commands.case": "lower",
"intersystems.language-server.formatting.commands.length": "long",
"intersystems.language-server.formatting.system.case": "lower",
"intersystems.language-server.formatting.system.length": "long",Thank you!
There was a problem hiding this comment.
Ah sorry I misremembered the rules. The rules look fine! They will not change variable capitalization so that will be up to us to catch any inconsistencies
There was a problem hiding this comment.
No worries. Thank you for the confirmation.
| </command> | ||
|
|
||
| <command name="update" dataPrefix="D"> | ||
| <summary>Updates a module to a newer version.</summary> |
There was a problem hiding this comment.
Suggest adding to update command description and summary that by default it will uninstall all orphaned modules
There was a problem hiding this comment.
Thank you. Additionally, uninstalls any modules that are no longer required as dependencies by default - added this in the description
|
|
||
| set status = ..CreateModuleFile(v1ModuleDir, "DemoModuleV1") | ||
| do $$$AssertStatusOK(status, mainModule_" version 1.0.0 Module.xml created") | ||
| do ..CreateDependecnyModules(v1ModuleDir) |
There was a problem hiding this comment.
nit: typo should be CreateDependencyModules
There was a problem hiding this comment.
Fixed the Method name typo. Thank you!
|
|
||
| do $$$LogMessage("Loading '" _ mainModule _ "' version 1.0.0") | ||
| set status = ..RunCommand("load "_v1ModuleDir) | ||
| do $$$AssertStatusOK(status, mainModule_" version 1.0.0 laoded successfully") |
There was a problem hiding this comment.
nit: typo should be "loaded"
There was a problem hiding this comment.
Fixed the typo.
| set depsCount = moduleObj.Dependencies.Count() | ||
| for i=1:1:depsCount{ | ||
| set depsName = moduleObj.Dependencies.GetAt(i) | ||
| do $$$LogMessage("Dependency module "_depsName.Name_" is exist") |
There was a problem hiding this comment.
nit: should be "Dependency module "depsName.Name" exists"
There was a problem hiding this comment.
fixed the message. Thank you!
| return status | ||
| } | ||
|
|
||
| Method CreateDependecnyModules(Directory As %String = "") |
There was a problem hiding this comment.
nit: typo in method name should be "Dependency"
There was a problem hiding this comment.
Updated the methodName. Thank you!
Description
This PR introduces automatic management of orphaned dependencies during the
updatecommand. When a module is updated and its new version no longer requires certain dependencies, those "orphaned" modules are now automatically uninstalled to maintain namespace hygiene. A new flag,-keep-orphans, has been added to allow users to override this behavior.fixes: #868
2. New Command Flag
-keep-orphansUninstallOrphanedDependenciesmethod is skipped, leaving all previously installed dependencies intact.output