Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- #992: Implement automatic history purge logic
- #973: Enables CORS and JWT configuration for WebApplications in module.xml
- #1059: Add automatic orphaned module cleanup to update command

### Fixed
- #1001: The `unmap` and `enable` commands will now only activate CPF merge once after all namespaces have been configured instead after every namespace
Expand Down
6 changes: 6 additions & 0 deletions src/cls/IPM/Main.cls
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ This command is an alias for `module-action module-name publish`
<summary>Updates a module to a newer version.</summary>
<description>
Updates a module to a newer version. By default, updates to the latest available version. Also runs all of the update steps from the current version to the newer version.
Additionally, uninstalls any modules that are no longer required as dependencies by default
This command is an alias for module-action module-name applyupdatesteps.
</description>

Expand All @@ -155,6 +156,10 @@ This command is an alias for `module-action module-name publish`
update HS.JSON 3.0.0
</example>

<example description="Updates the HS.JSON module from the current version to version 3.0.0 and keep the orphaned modules">
update HS.JSON 3.0.0 -keep-orphans
</example>

<!-- Parameters -->
<parameter name="module" required="true" description="Name of module on which to perform update actions" />
<parameter name="version" description="Version (or version expression) of module to update; defaults to the latest available if unspecified. Is ignored if -path is provided." />
Expand All @@ -165,6 +170,7 @@ This command is an alias for `module-action module-name publish`
<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" dataAlias="KeepOrphans" dataValue="1" description="Retains dependencies that are no longer required by the updated module. By default, these orphaned modules are automatically uninstalled." />
</command>

<command name="makedeployed">
Expand Down
62 changes: 61 additions & 1 deletion src/cls/IPM/Utils/Module.cls
Original file line number Diff line number Diff line change
Expand Up @@ -1129,11 +1129,21 @@ ClassMethod LoadNewModule(
$$$ThrowStatus($$$ERROR($$$GeneralError, msg))
}
}

set cmd = $get(params("cmd"))
set orphanCleanup = (cmd = "update") && ('$get(pParams("KeepOrphans")))
// Retrieve dependencies from the previous version and compare with the current version to find modules that are no longer required
if (orphanCleanup) {
set tModule = ##class(%IPM.Storage.Module).NameOpen(commandLineModuleName,,.tSC)
do ..GetExistingDependencies(tModule, .oldDependencies)
}
// This loads the new version of the module into storage. Overrides the module context of the currently installed module, if it exists.
set tSC = $system.OBJ.Load(pDirectory_"module.xml",$select(tVerbose:"d",1:"-d"),,.tLoadedList)
$$$ThrowOnError(tSC)

// Compare dependencies from the previous version with the latest version and collect orphaned dependencies as an array
if (orphanCleanup) {
do ..GetOrphanedDependencies(tModule,.oldDependencies, .orphanedDeps)
}
set tFirstLoaded = $order(tLoadedList(""))
if (tFirstLoaded = "") {
$$$ThrowStatus($$$ERROR($$$GeneralError,"No module definition found."))
Expand Down Expand Up @@ -1228,6 +1238,10 @@ ClassMethod LoadNewModule(
tcommit
}
$$$ThrowOnError(tSC)
// Uninstall orphaned dependencies
if (orphanCleanup) {
do ..UninstallOrphanedDependencies(.orphanedDeps, .pParams)
}
do tModule.WriteAfterInstallMessage()
} catch e {
set tSC = e.AsStatus()
Expand All @@ -1240,6 +1254,52 @@ ClassMethod LoadNewModule(
quit tSC
}

/// Uninstall orphaned dependencies identified during update.
/// Recursively removes modules not required by the main module or other local packages.
ClassMethod UninstallOrphanedDependencies(
ByRef OrphanedDeps,
ByRef Params)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: inconsistent capitalization of variables.
I suggest turning on the force-formatting rules found in IPM/.vscode/settings.json if working with VSCode.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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!

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No worries. Thank you for the confirmation.

{
if '$data(OrphanedDeps) {
quit
}
set dependencyModule=""
write !,"Uninstalling orphaned dependencies"
for {
set dependencyModule = $order(OrphanedDeps(dependencyModule)) quit:dependencyModule=""
continue:'##class(%IPM.Storage.Module).NameExists(dependencyModule)
// Remove this module and its dependencies recursively, skipping any still in use
$$$ThrowOnError(##class(%IPM.Storage.Module).Uninstall(dependencyModule,,1,.Params))
}
}

/// Retrieves the dependency graph for the currently installed version of the module.
ClassMethod GetExistingDependencies(
Module As %IPM.Storage.Module,
Output DependencyGraph)
{
$$$ThrowOnError(Module.BuildDependencyGraph(.DependencyGraph,,,,"",,,,,,))
}

/// Compares the previous dependency graph with the current one to identify orphans.
ClassMethod GetOrphanedDependencies(
Module As %IPM.Storage.Module,
ByRef ExistDependencies,
Output OrphanedModules)
{
kill OrphanedModules
$$$ThrowOnError(Module.BuildDependencyGraph(.dependencyGraph,,,,"",,,,,,))
set moduleName = ""
for {
set moduleName = $order(dependencyGraph(moduleName))
quit:moduleName=""

// Remove active dependencies from the existing list.
kill ExistDependencies(moduleName)
}
merge OrphanedModules = ExistDependencies
}

/// Load dependencies of a module in a synchronous manner, one at a time, in the correct order.
/// This method builds a dependency graph (optionally with phases) and installs missing dependencies.
ClassMethod LoadDependencies(
Expand Down
Loading