Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
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 @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- #822: The CPF resource processor now supports system expressions and macros in CPF merge files
- #578 Added functionality to record and display IPM history of install, uninstall, load, and update
- #961: Adding creation of a lock file for a module by using the `-create-lockfile` flag on install.
- #1013: Implement recursive placeholder resolution in Default parameters

### Changed
- #316: All parameters, except developer mode, included with a `load`, `install` or `update` command will be propagated to dependencies
Expand Down
1 change: 1 addition & 0 deletions src/cls/IPM/Storage/Module.cls
Original file line number Diff line number Diff line change
Expand Up @@ -1775,6 +1775,7 @@ Method %Evaluate(
set customParams("version") = ..VersionString
set customParams("verbose") = +$get(pParams("Verbose"))
set tAttrValue = ##class(%IPM.Utils.Module).%EvaluateMacro(tAttrValue)
do ##class(%IPM.Storage.ModuleSetting.Default).ResolvePlaceholders(.customParams)
set tAttrValue = ##class(%IPM.Storage.ModuleSetting.Default).EvaluateAttribute(tAttrValue,.customParams)
set attrValue = ##class(%IPM.Utils.Module).%EvaluateSystemExpression(tAttrValue)

Expand Down
34 changes: 34 additions & 0 deletions src/cls/IPM/Storage/ModuleSetting/Default.cls
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,40 @@ ClassMethod EvaluateAttribute(
return attribute
}

ClassMethod ResolvePlaceholders(ByRef customParams)
{
set found = 1
set maxLevels = 20

while (found && (maxLevels > 0)) {
set found = 0
set maxLevels = maxLevels - 1
set param = ""

for {
set param = $order(customParams(param), 1, data)
quit:param=""
continue:data'["${"

set varExpr = "${" _ $piece($piece(data, "${", 2), "}") _ "}"
set resolved = ##class(%IPM.Utils.Module).%EvaluateSystemExpression(varExpr)

if resolved = varExpr {
set internalVar = $piece($piece(data, "${", 2), "}")
set resolved = $get(customParams(internalVar), varExpr)
}

if resolved '= varExpr {
set prefix = $piece(data, "${", 1)
set suffix = $piece(data, "}", 2, *)
set customParams(param) = prefix _ resolved _ suffix
set found = 1
}

}
}
}

Storage Default
{
<Data name="DefaultState">
Expand Down
88 changes: 88 additions & 0 deletions tests/integration_tests/Test/PM/Integration/Module.cls
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
Class Test.PM.Integration.Module Extends Test.PM.Integration.Base
{

Parameter CommonPathPrefix As STRING = "varresolver";

Parameter ModuleName As STRING = "demo-module1";

/// This test validates that the IPM engine can handle "chained" ${variables}
/// (placeholders that resolve to other placeholders).
/// 1. Generates a 'module.xml' from XData with deep variable dependencies.
/// 2. Executes IPM Shell 'load' to verify multi-pass expansion.
/// 3. Ensures no unresolved placeholders remain after the load process.
Method TestNestedPlaceholderVar()
{
do $$$LogMessage(" start Loading the "_..#ModuleName_" module")
set status = ..CreateModuleXml(.moduleDir)
do $$$AssertStatusOK(status,"Created the xml file on "_moduleDir)

set status = ##class(%IPM.Main).Shell("load "_moduleDir)
do $$$AssertStatusOK(status,"Loaded "_..#CommonPathPrefix_" module successfully from "_moduleDir)

set module = ##class(%IPM.Storage.Module).NameOpen(..#ModuleName)
do $$$AssertTrue($isobject(module), "Module "_..#ModuleName_" exists in IPM and version is "_ module.Version.ToString())

do $$$LogMessage("List all modules")
set status = ##class(%IPM.Main).Shell("list")

set status = ##class(%IPM.Main).Shell("uninstall "_..#ModuleName)
do $$$AssertStatusOK(status,"uninstalled module "_..#ModuleName_" successfully.")

set status = ##class(%File).Delete(##class(%File).NormalizeFilename("module.xml",moduleDir))
do $$$AssertStatusOK(status,"Deleted the module.xml file from "_moduleDir)
}

Method CreateModuleXml(Output pModuleDir) As %Status
{
#define NormalizeDirectory(%path) ##class(%File).NormalizeDirectory(%path)
#define UTRoot ^UnitTestRoot

set sc = 1
set testRoot = $$$NormalizeDirectory($get($$$UTRoot))
set pModuleDir = $$$NormalizeDirectory(##class(%File).GetDirectory(testRoot)_"/_data/"_..#CommonPathPrefix_"/")

if '##class(%File).DirectoryExists(pModuleDir) {
set sc = ##class(%File).CreateDirectoryChain(pModuleDir)
}
do $$$AssertStatusOK(sc,"Directory created "_pModuleDir)
set stream = ##class(%Dictionary.CompiledXData).%OpenId(..%ClassName(1)_"||TestModuleXML").Data
set fileStream = ##class(%Stream.FileBinary).%New()
set fileStream.Filename=##class(%File).NormalizeFilename("module.xml",pModuleDir)
set sc = fileStream.CopyFromAndSave(stream)
do $$$AssertStatusOK(1,"module.xml File created on "_pModuleDir)

return sc
}

/// Sample module file
XData TestModuleXML [ MimeType = application/xml ]
{
<?xml version="1.0" encoding="UTF-8"?>
<Export generator="Cache" version="25">
<Document name="demo-module1.ZPM">
<Module>
<Name>demo-module1</Name>
<Version>1.0.0</Version>
<Description>testing the name resolved</Description>
<Packaging>module</Packaging>
<Default Name="count" Value="7"/>
<Default Name="dataversion" Value="${version}/" />
<Default Name="datadefaultmgrdir" Value="${mgrdir}/" />
<Default Name="mTestPlaceHolder" Value="${dataDefaultTest2}/xtest/${version}/${mgrdir}/${mTestVersion}/${datapath1}" />
<Default Name="datadefaultcspdir" Value="${cspdir}/" />
<Default Name="dataDefaultTest2" Value="${datadefaultcspdir}/xdata" />
<Default Name="dataDefaultTest3" Value="${dataDefaultTest2}/xtest" />
<Default Name="mTestVersion" Value="${dataDefaultTest2}/xtest/${version}" />
<Default Name="ipmtest" Value="TESTING MY STRING"/>
<Default Name="ipmdir" Value="/usr/irissys/mgr/user/mts"/>
<Default Name="datapath" Value="${libdir}data/" />
<Default Name="datapath1" Value="${ipmdir}data/" />
<SystemRequirements Version=">=2020.1" Interoperability="enabled"/>
<SourcesRoot>src</SourcesRoot>
<AfterInstallMessage>Module installed successfully!</AfterInstallMessage>
</Module>
</Document>
</Export>
}

}
43 changes: 43 additions & 0 deletions tests/unit_tests/Test/PM/Unit/Module.cls
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,47 @@ Method TestFixUndefinedCLIGenCommand()
do $$$AssertStatusOK(sc, "AddWebApps method must now process web app list without error.")
}

Method TestResolveAllVariables()
{
//Setup Test Data
set customParams("count") = 7
set customParams("version") = "1.0.0"
set customParams("datadefaultcspdir") = "${cspdir}"
set customParams("datadefaultmgrdir") = "${mgrdir}"
set customParams("dataversion") = "${version}"
set customParams("dataDefaultTest2") = "${datadefaultcspdir}xdata"
set customParams("dataDefaultTest3") = "${dataDefaultTest2}xtest"
set customParams("datapath") = "${libdir}data/"
set customParams("ipmdir") = "/usr/irissys/mgr/user/mts"
set customParams("datapath1") = "${ipmdir}data/"
set customParams("mTestVersion") = "${dataDefaultTest2}/xtest/${version}"
set customParams("mTestPlaceHolder") = "${dataDefaultTest2}/xtest/${version}${mgrdir}${mTestVersion}${datapath1}"

merge customParamsIn = customParams
// output
set customParamsOut("count")=7
set customParamsOut("dataDefaultTest2")="/usr/irissys/csp/xdata"
set customParamsOut("dataDefaultTest3")="/usr/irissys/csp/xdataxtest"
set customParamsOut("datadefaultcspdir")=##class(%IPM.Utils.Module).%EvaluateSystemExpression("${cspdir}")
set customParamsOut("datadefaultmgrdir")=##class(%IPM.Utils.Module).%EvaluateSystemExpression("${mgrdir}")
set customParamsOut("datapath")=##class(%IPM.Utils.Module).%EvaluateSystemExpression("${libdir}")_"data/"
set customParamsOut("datapath1")="/usr/irissys/mgr/user/mtsdata/"
set customParamsOut("dataversion")="1.0.0"
set customParamsOut("ipmdir")="/usr/irissys/mgr/user/mts"
set customParamsOut("mTestPlaceHolder")="/usr/irissys/csp/xdata/xtest/1.0.0/usr/irissys/mgr//usr/irissys/csp/xdata/xtest/1.0.0/usr/irissys/mgr/user/mtsdata/"
set customParamsOut("mTestVersion")="/usr/irissys/csp/xdata/xtest/1.0.0"
set customParamsOut("version")="1.0.0"

do ##class(%IPM.Storage.ModuleSetting.Default).ResolvePlaceholders(.customParams)

do $$$LogMessage("Validate all placholder variables")

set variables = $listbuild("count","dataDefaultTest2","dataDefaultTest3","datadefaultcspdir","datadefaultmgrdir","datapath","datapath1","dataversion","ipmdir","mTestPlaceHolder","mTestVersion","version")

set ptr=0
while $listnext(variables, ptr, variable){
do $$$AssertEquals(customParams(variable), customParamsOut(variable), variable_" is resolved correctly and the placeholder "_customParamsIn(variable)_" value is "_customParamsOut(variable))
}
}

}