Skip to content
Merged
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
- #1001: The `unmap` and `enable` commands will now only activate CPF merge once after all namespaces have been configured instead after every namespace
- #1052: In a namespace with mapped IPM, the `info` command works again and the intro message displays the IPM version and where its mapped from
- #1102: %IPM.Storage.QualifiedModuleInfo:%New() will now copy over version properties when passed in a resolvedReference
- #1112: Packaging a module with a globals resource now respects SourcesRoot, placing the exported file at the correct path in the tarball

## [0.10.6] - 2026-02-24

Expand Down
61 changes: 41 additions & 20 deletions src/cls/IPM/ResourceProcessor/Default/Global.cls
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Class %IPM.ResourceProcessor.Default.Global Extends %IPM.ResourceProcessor.Abstr
Parameter DESCRIPTION As STRING = "Standard resource processor for global exports.";

/// Comma-separated list of resource attribute names that this processor uses
Parameter ATTRIBUTES As STRING = "Global,Preserve";
Parameter ATTRIBUTES As STRING = "Global,Preserve,Directory,FilenameTranslateIdentifier,FilenameTranslateAssociator";

/// Optional name of global within the file
Property Global As %IPM.DataType.GlobalReference;
Expand All @@ -17,6 +17,12 @@ Property Preserve As %Boolean [ InitialExpression = 0 ];
/// Defaults to the resource's extension (lower-case) if unspecified.
Property Directory As %String(MAXLEN = "") [ InitialExpression = "gbl/" ];

/// Characters in the resource name to translate when building the filename.
Property FilenameTranslateIdentifier As %String [ InitialExpression = "%,("")" ];

/// Replacement characters corresponding to FilenameTranslateIdentifier.
Property FilenameTranslateAssociator As %String [ InitialExpression = "___" ];

Method OnPhase(
pPhase As %String,
ByRef pParams,
Expand Down Expand Up @@ -51,30 +57,16 @@ Method OnPhase(
}

if '..ResourceReference.Generated {
set tSubDirectory = $select(..ResourceReference.Preload:"preload/",1:"")
set tResourceDirectory = tRoot _ "/" _ tSubDirectory

set tSourceRoot = ..ResourceReference.Module.SourcesRoot
if tSourceRoot'="","\/"'[$extract(tSourceRoot, *) {
set tSourceRoot = tSourceRoot _ "/"
}

set tDirectory = ..Directory
if tDirectory'="","\/"'[$extract(tDirectory, *) {
set tDirectory = tDirectory _ "/"
} else {
set tDirectory = "gbl/"
}

set tResourceDirectory = ##class(%File).NormalizeDirectory(tResourceDirectory_tSourceRoot_tDirectory)
set relativePath = ..OnItemRelativePath(..ResourceReference.Name)
set resourcePath = ##class(%File).NormalizeFilename(tRoot _ "/" _ relativePath)
set resourceDirectory = ##class(%File).GetDirectory(resourcePath)

if tDeveloperMode {
set ^Sources("GBL",tName) = tSourcesPrefix_tResourceDirectory
set ^Sources("GBL", tName) = tSourcesPrefix _ resourceDirectory
}

if '..ResourceReference.Preload {
set tResourcePath = tResourceDirectory_$translate(tName,"%,("")","___")_".xml"
set tSC = $system.OBJ.Load(tResourcePath,$select(tVerbose:"/display",1:"/nodisplay")_"/nocompile")
set tSC = $system.OBJ.Load(resourcePath, $select(tVerbose:"/display", 1:"/nodisplay") _ "/nocompile")
if $$$ISERR(tSC) {
quit
}
Expand All @@ -93,4 +85,33 @@ Method OnPhase(
quit tSC
}

/// Sets the relative path for the globals resource so the packaging phase
/// exports the file to the correct location, respecting SourcesRoot.
Method OnResolveChildren(ByRef resourceArray) As %Status
{
set resourceArray(..ResourceReference.Name, "RelativePath") = ..OnItemRelativePath(..ResourceReference.Name)
quit $$$OK
}

/// Returns the path of <var>itemName</var> relative to the module root,
/// including SourcesRoot and the configured Directory.
Method OnItemRelativePath(itemName As %String) As %String
{
set sourceRoot = ..ResourceReference.Module.SourcesRoot
// Append trailing slash only if sourceRoot is non-empty and doesn't already end with / or \
if (sourceRoot '= "") && ("\/" '[ $extract(sourceRoot, *)) {
set sourceRoot = sourceRoot _ "/"
}

set directory = ..Directory
if directory = "" {
set directory = "gbl/"
} elseif "\/" '[ $extract(directory, *) {
set directory = directory _ "/"
}

set name = $piece(itemName, ".", 1, * - 1)
quit $select(..ResourceReference.Preload:"preload/", 1:"") _ sourceRoot _ directory _ $translate(name, ..FilenameTranslateIdentifier, ..FilenameTranslateAssociator) _ ".xml"
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
Class Test.PM.Integration.GlobalsPackaging Extends Test.PM.Integration.Base
{

/// Verifies that packaging a module with a globals resource and SourcesRoot
/// exports the file to the correct path and produces a loadable tarball.
Method TestGlobalsPackageWithSourcesRoot()
{
set sc = $$$OK
try {
set moduleDir = ..GetModuleDir("globals")

// Ensure clean state before loading from source
do ##class(%IPM.Main).Shell("uninstall globals-test")

set sc = ##class(%IPM.Main).Shell("load " _ moduleDir)
do $$$AssertStatusOK(sc, "Loaded globals-test module successfully.")

set tempDir = ##class(%Library.File).TempFilename() _ "dir"
set sc = ##class(%IPM.Main).Shell("globals-test package -DPath=" _ tempDir)
do $$$AssertStatusOK(sc, "Packaged globals-test module successfully.")

set outDir = ##class(%Library.File).NormalizeDirectory(##class(%Library.File).TempFilename() _ "dir-out")
set sc = ##class(%IPM.General.Archive).Extract(tempDir _ ".tgz", outDir, .extractOutput)
do $$$AssertStatusOK(sc, "Extracted tarball.")

do $$$AssertTrue(##class(%File).Exists(outDir _ "src/gbl/My.Settings.xml"), "Global exported to src/gbl/My.Settings.xml (SourcesRoot respected).")

// Uninstall before loading from tarball to get a clean load assertion
do ##class(%IPM.Main).Shell("uninstall globals-test")

set sc = ##class(%IPM.Main).Shell("load " _ tempDir _ ".tgz")
do $$$AssertStatusOK(sc, "Loaded globals-test from tarball successfully.")

do $$$AssertEquals($get(^My.Settings("Parameter")), 42, "^My.Settings imported correctly from tarball.")
} catch e {
do $$$AssertStatusOK(e.AsStatus(), "An exception occurred.")
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<Export generator="Cache" version="25">
<Document name="globals-test.ZPM">
<Module>
<Name>globals-test</Name>
<Version>1.0.0</Version>
<Packaging>module</Packaging>
<SourcesRoot>src</SourcesRoot>
<Resource Name="My.Settings.GBL"/>
</Module>
</Document>
</Export>
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<Export generator="IRIS" version="26">
<Global>
<Node><Sub>^My.Settings</Sub>
<Node><Sub>Mode</Sub>
<Data>Example</Data>
</Node>
<Node><Sub>Parameter</Sub>
<Data>42</Data>
</Node>
</Node>
</Global>
</Export>
Loading