03. IPM Manifest (module.xml) - intersystems/ipm GitHub Wiki
module.xml is analagous to (for example) package.json in npm or pom.xml in Maven. It provides metadata about the package like its name and version, states the package's direct dependencies, and also enumerates the components of the package and how they should be treated on installation. For example:
- Load the classes in a given folder when the package is installed
- Run unit or integration tests from a given folder when instructed to "test" or "verify" the package
- Create a web application
- Run an ObjectScript classmethod after installation, or when a specific command is run (like an npm "script")
IPM uses semantic versioning, and as a package maintainer you should update your package version in keeping with the semantic versioning standard. You may use prerelease versions (1.0.0-alpha.1, 1.0.0-beta.2, 1.0.0-SNAPSHOT) and/or associate build metadata (1.0.0-SNAPSHOT+20250408), where prerelease versions do have an impact on version ordering/preference and build metadata as a rule does not.
When declaring dependencies, semantic version ranges are supported in the same syntax as node-semver. For example, to accept any version compatible (from a semantic versioning standpoint) with 1.0.1
, you would declare a dependency on version ^1.0.1
. To accept a version 1.x or 2.x, you might declare a dependency on version >=1.0.0 && <3.0.0
.
If you're writing module.xml
for a new package, there are two great ways to start:
- Copy one from an existing internal or Open Exchange package that looks similar to what you need
- Run the
zpm "generate"
command for interactive prompts - see the CLI reference for more information.
<?xml version="1.0" encoding="UTF-8"?>
<Export generator="Cache" version="25">
<Document name="demo-module.ZPM">
<Module>
<Name>demo-module</Name>
<Version>1.0.0</Version>
<Description>description</Description>
<Keywords>keywords</Keywords>
<Author>
<Person>your name</Person>
<Organization>your organization</Organization>
<CopyrightDate>2020</CopyrightDate>
<License>MIT</License>
<Notes>notes</Notes>
</Author>
<Packaging>module</Packaging>
<Dependencies>
<ModuleReference>
<Name>MDX2JSON</Name>
<Version>2.2.*</Version>
</ModuleReference>
</Dependencies>
<Default Name="count" Value="7" />
<SystemRequirements Version=">=2020.1" Interoperability="enabled" />
<SourcesRoot>src</SourcesRoot>
<Resource Name="Demo.PKG"/>
<Resource Name="REST.Dispatch.CLS"/>
<Resource Name="Demo.test.GBL"/>
<Resource Name="Myapp.MAC" Directory="newmac"/>
<FileCopy Name="lib/" Target="${libdir}my-lib/"/>
<FileCopy Name="somefile.jar" Target="${libdir}my-lib/"/>
<UnitTest Name="/tests/unit_tests/" Package="Test.Unit" Phase="test"/>
<SystemSetting Name="CSP.DefaultFileCharset" Value="UTF-8"/>
<Invoke Class="Demo.First" Method="Populate"></Invoke>
<Invoke Class="Demo.First" Method="Populate">
<Arg>${count}</Arg>
<Arg>test string</Arg>
</Invoke>
<Invoke Class="%EnsembleMgr" Method="EnableNamespace" Phase="Compile" When="Before" CheckStatus="true">
<Arg>${namespace}</Arg>
<Arg>${verbose}</Arg>
</Invoke>
<CSPApplication
Url="/hello"
SourcePath="/src/csp"
DeployPath="${cspdir}rest-test"
ServeFiles="1"
Recurse="1"
CookiePath="/hello"
UseCookies="2"
MatchRoles=":${dbrole}"
PasswordAuthEnabled="1"
UnauthenticatedEnabled="0"
/>
<CSPApplication
Url="/rest-test"
Recurse="1"
MatchRoles=":${dbrole}"
PasswordAuthEnabled="1"
UnauthenticatedEnabled="0"
DispatchClass="REST.Dispatch"
ServeFiles="1"
CookiePath="/rest-test"
UseCookies="2"
/>
<AfterInstallMessage>Module installed successfully! To start call USER>Do ##class(Package.Class).Run()</AfterInstallMessage>
</Module>
</Document>
</Export>
The root element; might also see <Export generator="IRIS" version="26">
. This is a boilerplate wrapper to work with $System.OBJ.Load, indicating the document is exported and importable by IRIS. Under the hood, this uses %Studio.AbstractDocument
to define a type of document editable through IDEs (VSCode, Studio).
In the name attribute, specify your module name with a .ZPM suffix. This defines the name of the document within IRIS and must match the name in the <Name>
element under <Module>
.
<Document name="demo-module.ZPM">
Parent element for all module metadata
Module name. Note: whitespace characters are not allowed.
<Name>demo-module</Name>
Module version, following semantic versioning
<Version>1.0.0</Version>
This is essentially always set to module
, which just means "the artifact for this package is a compressed archive of the raw source contents and should be treated as such".
One of the inspirations for IPM is Maven, which supports different forms of packaging; see e.g. https://www.baeldung.com/maven-packaging-types.
A package can also be packaged as a Studio Project (see https://github.com/intersystems/ipm/blob/main/src/cls/IPM/Lifecycle/StudioProject.cls). At some point we may also revisit packaging as IRIS.DAT / pure file-based artifacts, which was part of the original design for IPM.
Contains all module dependencies. Specify each dependent module in a <ModuleReference>
element.
In the Version
element for a dependency you can specify an exact version (1.2.36) or use a semantic version expression in the familiar syntax of node-semver.
. For example:
<Version>2.2.*</Version>
- means the latest version grater than 2.2.0 and less then 2.3.0
<Version>^2.2.1</Version>
- means anything compatible with 2.1.1 - that is, >=2.1.1 and <= 3.0.0
<Version>*</Version>
- means any version (though the latest will be preferred)
Custom parameter with its default values
You can use these parameters to transfer data to the module installer during installation.
If you define a parameter
<Default Name="count" Value="7" />
you can pass a parameter value in install or load command
zpm: USER>install demo-module -Dzpm.count=12
Use Version
attribute to specify IRIS versions, supported by your module.
Use Interoperability
attribute to indicate that your module requires Interoperability.
<SystemRequirements Version=">=2020.1" Interoperability="enabled" />
Specify the directory where code of the module is placed
This element is required
<SourcesRoot>src</SourcesRoot>
Use the following suffixes for different types of resources:
-
.PKG
- Package -
.CLS
- Class -
.INC
- Include -
.MAC
- Routine -
.LOC
- LocalizedErrorMessages -
.GBL
- Global -
.DFI
- DeepSee Item -
<nosuffix>
- document
-
Deploy
- If set to true, this resource is not shipped in application packaging.
-
Directory
- Directory the resource is located in. If not specified, uses the default folders in
/src
.-
/cls
- for Classes (.PKG, CLS) -
/mac
- for Routines -
/inc
- for Include
-
- Example:
<SourcesRoot>src</SourcesRoot> <Resource Name="REST.Dispatch.CLS" /> <Resource Name="Myapp.MAC" Directory="newmac"/>
- Directory the resource is located in. If not specified, uses the default folders in
-
Flags
- Compile flags (see documentation here)
- Example:
<Resource Name="MyPackage.PKG" Flags="cku"/>
-
Generated
- Specify if this resource is generated. A generated resource will not be loaded nor exported by lifecycle methods.
-
Name
- Name of the resource (required)
- Examples:
<Resource Name="MyPackage.objectscript.PKG"/>
<Resource Name="MyPackage.MyFile.INC"/>
<Resource Name="/doc/README.txt"/>
-
Preload
- Specify if this resource is in the
preload
directory to be loaded before other resources. Useful for installers. - Example:
<Resource Name="MyPackage.Installer.CLS" Preload="true"/>
- Specify if this resource is in the
-
ProcessorClass
- Specifies a class that handles lifecycle events for this resource.
- Typically only applicable to directory/file resources.
- See here for options
-
Scope
- Restricts the scope in which this resource is included. Default is all scopes.
- Options:
test
verify
If additional/custom name-value pairs are required (generally used for custom resource processor classes), they can be specified as attributes of the resource element, e.g.:
<Resource Name="/whatever/myfile.foo" ProcessorClass="MyLibrary.FooProcessor" CustomAttribute="SomeValue" SomeBoolean="1" AnotherSetting="25" />
Each <Invoke>
indicates which Class Method should be called at a specified phase (e.g. during installation)
Specify method arguments with <Arg>
if necessary. Values in <Arg>
can be either primitives or system expressions.
<Invoke Class="%EnsembleMgr" Method="EnableNamespace" Phase="Compile" When="Before" CheckStatus="true">
<Arg>${namespace}</Arg>
<Arg>${verbose}</Arg>
</Invoke>
Invoke attributes:
-
Class
(required): Class for which method is to be run. -
Method
(required): Name of class method to run. -
CheckStatus
(Default: false): expects that invoked method returns %Status, and use it to check the status of installation. -
Phase
(Default: Configure): the phase during which it should be executed. -
CustomPhase
(as of v1): If provided, the Phase property will be ignored. This CustomPhase will be used and no corresponding lifecycle is required. This can be used to run a custom phase of a module that does not run during any regular OOTB IPM command (e.g. if you want to run your custom phasemy-phase
, you can do so using the command -zpm "MyModule my-phase"
). -
When
(Default: After), values: Before and After: To execute before or after the main execution for the phase.
AfterInstallMessage tag is intended to message package users next steps after successful installation.
This message will appear in terminal after all the package's resources processing.
It should be placed insude <Module>
tag.
Example:
<AfterInstallMessage>Module installed successfully! To start call USER>Do ##class(Package.Class).Run()</AfterInstallMessage>
The ^IRIS.msg global can be automatically populated using .LOC
resources. (See here for more information on usage of the global within IRIS.)
<?xml version="1.0" encoding="UTF-8"?>
<Export generator="Cache" version="25">
<Document name="loc-example.ZPM">
<Module>
<Name>loc-example</Name>
<Version>1.0.0</Version>
<Packaging>module</Packaging>
<SourcesRoot>src</SourcesRoot>
<Resource Name="My.Errors.LOC"/>
<Resource Name="en.loc"/>
</Module>
</Document>
</Export>
<?xml version="1.0" encoding="UTF-8"?>
<Export generator="IRIS" version="26">
<Document name="demo.LOC">
<MsgFile Language="en">
<MsgDomain Domain="myapp">
<Message Id="Information">Some Information</Message>
<Message Id="Description">Some Description</Message>
</MsgDomain>
</MsgFile>
</Document>
</Export>
<?xml version="1.0" encoding="UTF-8"?>
<Export generator="IRIS" version="26">
<Document name="My.Errors.LOC">
<MsgFile Language="en">
<MsgDomain Domain="MyErr">
<Message Id="CustomError">Value %1 is invalid</Message>
</MsgDomain>
</MsgFile>
</Document>
</Export>
^IRIS.Msg("MyErr","en","CustomError")="Value %1 is invalid"
^IRIS.Msg("myapp","en","Description")="Some Description"
^IRIS.Msg("myapp","en","Information")="Some Information"
Globals can be added to a package using a .GBL
resource in module.xml
and adding an appropriate .xml
file in the src/gbl
directory.
<?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>
<?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>
^My.Settings("Mode")="Example"
^My.Settings("Parameter")=42
Case-insensitive expressions that can be used in strings. Note that the $
symbol in the expression can be located either inside or outside the curly braces, e.g. {$ns}
is equivalent to ${ns}
.
-
{name}
- the name of the current resource. Notice that it doesn't have a $ sign. -
${ns}
- the current namespace. -
${namespace}
- the current namespace. -
${namespaceLower}
- the current namespace in lower case. -
${namespaceRoutineDB}
- the default routines database for the current namespace. -
${namespaceGlobalsDB}
- the default globals database for the current namespace. -
${installDir}
- the instance's install directory (i.e.$System.Util.InstallDirectory()
) -
${dataDir}
- the instance's data directory (i.e.$System.Util.DataDirectory()
) -
${mgrDir}
- the instance's mgr directory (i.e.##class(%Library.File).ManagerDirectory()
) -
${cspDir}
- the instance's root CSP directory (i.e.$System.Util.InstallDirectory()_"csp"
) -
${binDir}
- the instance's bin directory (i.e.$System.Util.BinaryDirectory()
) -
${libDir}
- the instance's lib directory (i.e.$System.Util.InstallDirectory()_"lib"
) -
${webroot}
- the instance's constructed url with host and port (e.g. http://123.45.678.90:52773/) -
${dbrole}
- (deprecated in favor of ${globalsDbRole}) the current namespace's globals database role -
${globalsDbRole}
- the current namespace's globals database role, functionally equivalent to ${dbrole} in legacy packages. -
${root}
- the resource module's root directory -
${packagename}
- the name of the package -
${version}
- the version string of the package -
In particular, you can also
$$$macro
s will be expanded. But the macro cannot contain any arguments. For example,$$$OK
will be expanded to1
, while$$$ThrowOnError(##class(xxx).yyy())
won't be correctly handled.
Examples:
- If the install directory is
C:\InterSystems\IRIS\
, then${installDir}
evaluates toC:\InterSystems\IRIS\
,${cspDir}
evaluates toC:\InterSystems\IRIS\CSP
, and${libDir}
evaluates toC:\InterSystems\IRIS\lib
-
Resource
attributes can make use of these expressions like this:
<Resource Name="MyResource">
<Attribute Name="Directory">${cspdir}${namespace}/my-directory</Attribute>
</Resource>
Resource processors can always be specified using the <Resource name=MyName ProcessorClass=MyProcessorClass>
syntax, but default resource processors (listed below) can be specified using the <<processor-class>>
syntax, e.g. <UnitTest>
and <CSPApplication>
.
- Copies the specified resource from the (Source) database to the target namespace (respecting mappings configured for that namespace) during the Reload phase.
- Attributes
-
Source
- name of the namespace to be copied from (required) -
Overwrite
- if set to false (default is true), resources that already exist in the current/target namespace will not be overwritten
-
Each WebApplication
defines a generic web application. It can be a CSP Application, a WSGI Application, or any other future application described in the InterSystems documentation of Security.Applications.
To define a WebApplication, look up the attributes of the properties listed here first. Then, include the properties as tags in the <WebApplication />
element. Notice that
- You can use system expressions like
${variable}
,{$variable}
,#{ expr }
described in the previous section. - You can use macros like
{$$$AutheCache}
described in the previous section, provided the macro is existent on the user's IRIS instance. If you want to restrict IRIS version your package may be installed on, use syntax like<SystemRequirements Version=">=2024.1">
at the top level ofmodule.xml
. -
Url
is an alias for theName
tag. When both a present,Name
will be used. When neither a present, an error will be raised. This alias makes it possible in many cases to just rename a deprecatedCSPApplication
toWebApplication
inmodule.xml
.
For example, to define a CSP web application at https://<your-instance>/restdemo
, you can use
<WebApplication
Name="/restdemo"
NameSpace="{$namespace}"
Path="/src"
Recurse="1"
Directory="{$cspdir}/restdemo"
MatchRoles=":${dbrole}:%SQL:%All:%Developer,%Manager:%All"
AutheEnabled="#{$$$AutheCache + $$$AutheUnauthenticated}"
DispatchClass="Test.Rest.Demo"
ServeFiles="1"
CookiePath="/restdemo"
UseCookies="2"
/>
Similarly, to define a WSGI web application (on 2024.1 and later), you can use
<WebApplication
Url="/my/flask/demo"
AutheEnabled="#{ 32 + ${authUnauthenticated} }"
Description="Sample WSGI application using Flask"
MatchRoles=":${dbrole}:%SQL:%Developer,%Manager:%All"
NameSpace="${ns}"
WSGIAppLocation="${libdir}flask-demo/flaskapp/"
WSGIAppName="app"
WSGICallable="app"
DispatchClass="%SYS.Python.WSGI"
/>
Make sure to use FileCopy
to copy the Python source code to WSGIAppLocation
first.
<CSPApplication/>
has been deprecated in IPM 0.9.0 and later. If you have this in your module.xml, consider changing to <WebApplication/>
instead. In most cases, simply renaming the element will work.
Each <CSPApplication>
defines a web application.
For CSP/ZEN applications required attributes:
-
Url
- application name -
Path
- source folder with .csp files -
Directory
- destination directory (to which files will be copied during installation)
For REST applications required attributes:
-
Url
- application name -
DispatchClass
- class name
Use attributes as they are described for <CSPApplication>
tag here
Grant
attribute is replaced by MatchRoles
.
Mapping of Match Roles to added Target Roles.
MatchRoles are in the format:
MatchRole:TargetRole1:TargetRole2
To specify a role to always be granted to an application, set MatchRole="", i.e. (:TargetRole1)
Additional attributes that can be used instead of AuthenticationMethods:
PasswordAuthEnabled
UnauthenticatedEnabled
DelegatedAuthEnabled
KerberosAuthEnabled
Additional attributes
Enabled
DeepSeeEnabled
iKnowEnabled
- Copies the specified directory or file (the resource name) to a specific target location (
InstallDirectory
) during theActivate
phase. - Attributes:
-
InstallDirectory
(orTarget
orDest
) - Path to which the directory or file (a full filename, in that case) should be copied upon installation; may contain expressions -
Overlay
- If true, the files should be added to the target location (rather than fully replacing it, causing other files there to be deleted). Relevant for directories only. -
CSPApplication
- Optional hint to source control class: which CSP application path do these files map to? For use cases where the CSPApplication resource processor is too heavy-handed - e.g., /csp/xslt. Of course, this is only relevant for files that need to be copied to a CSP application. Note that this may be a full CSP file path, not only the name of a CSP application. -
Defer
- If true, the files will be copied at the end of the Activate phase rather than at the beginning. The default is to copy the files at the beginning of the Activate phase. Use this for build artifacts.
-
- Examples
-
<FileCopy Name="lib/" Target="${libdir}my-lib/"/>
Copies content of lib folder to Target -
<FileCopy Name="somefile.jar" Target="${libdir}my-lib/"/>
Copies just desired file to Target -
You can use
${libdir}
or${bindir}
forinstalldir/lib
andinstalldir/bin
respectively. See the System Expressions section for more details. -
If the Name ends with a slash, it will be copied as a folder. During uninstall, the Target folder will be deleted
<FileCopy Name="dsw/" Target="${cspdir}dsw/configs/"/>
-
- Processes a message dictionary export of error message names, generating an include file with macros to represent them.
- Alternative to
Default.LocalizedMessages
, which is automatically used for.LOC
files. - Attributes
-
includeFiles
- Acceptable Formats:<include file name>
<domain>:<include file name>[,<domain>:<include file name>[,<domain>:<include file name>...]]
-
merge
- Set to 1 to merge the domain (i.e., in ^IRIS.Msg(domain)/^IRIS.MsgNames(domain)) with contents loaded from other error message XML files. Additional work will likely be required to make the domain available for localization in such cases, likely using the LocalizationExport resource processor.
-
- Resource processor to automatically export messages in given list of domains to a particular file after compilation
- This works with a resource like /localize/MessageFileName.xml (Domains attribute required; resource name used as export target), or MyPackage.Errors.LOC (Filename attribute required; Domains populated automatically from the message file)
- For .LOC resources, the default resource processor class (%IPM.ResourceProcessor.Default.LocalizedMessages) should be used instead.
- In the Activate phase, exports the in-database code resources of this module (classes, routines, include files) as a Studio project in a single XML file.
- Attributes
-
TargetFile
- file to which the module should be exported (required)
-
Specifies the unit tests to be loaded and run. In development mode modules, unit tests are always loaded and not deleted after they are run.
-
Package
- the package containing the unit tests -
Class
- the class containing the unit tests (note: eitherPackage
orClass
must be defined) -
Name
- the directory that the package or class is located in- e.g. for
<UnitTest Name="/tests/unit_tests/" Package="Test.Unit" Phase="test"/>
, the unit tests will in/test/unit_tests/Test/Unit/
- e.g. for
-
Phase
- the phase the tests will run in-
test
: development namespace -
verify
: new, separate, clean namespace -
test,verify
: run in both cases
-
Different unit tests can be specified for different phases:
<UnitTest Name="/tests/unit_tests/" Package="Test.Unit" Phase="test"/>
<UnitTest Name="/tests/integration_tests/" Package="Test.Integration" Phase="verify"/>
These tests can be run using test <module>
for test phase tests and verify <module>
for verify phase tests.