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 @@ -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))
}
}

}