Open Exchange App The Anatomy of ZPM Module: Packaging Your InterSystems Solution

Hi Developers!

Recently we launched InterSystems Package Manager - ZPM. And one of the intentions of the ZPM is to let you package your solution and submit into the ZPM registry to make its deployment as simple as "install your-package" command.

To do that you need to introduce module.xml file into your repository which describes what is your InterSystems IRIS package consists of.

This article describes different parts of module.xml and will help you to craft your own.

I will start from samples-objectscript package, which installs into IRIS the Sample ObjectScript application and could be installed with:

zpm: USER>install samples-objectscript

It is probably the simplest package ever and here is the module.xml which describes the package:

<?xml version="1.0" encoding="UTF-8"?>
<Export generator="Cache" version="25">
  <Document name="samples-objectscript.MODULE">
    <Module>
      <Name>samples-objectscript</Name>
      <Version>1.0.0</Version>
      <Packaging>module</Packaging>
      <SourcesRoot>src</SourcesRoot>
      <Resource Name="ObjectScript.PKG"/>
    </Module>
  </Document>
</Export>

Let's go line-by-line through the document.

<Export generator="Cache" version="25">

module.xml belongs to the family of Cache/IRIS xml documents so this line states this relation to let internal libraries to recognize the document.

Next section is <Document> 

  <Document name="samples-objectscript.MODULE">

Your package should have a name. The name can contain letters in lower-case and "-" sign. E.g. samples-objectscript in this case. please put the name of your package in the name clause of the Document tag with the .MODULE extension.

Inner elements of the Document are:

<Name> - the name of your package. In this case:

<Name>samples-objectscript</Name>

<Version> - the version of the package. In this case:

<Version>1.0.0</Version>

<Packaging>module</Packaging> - the type of packaging. Put the module parameter here.

<Packaging>module</Packaging>

<SourcesRoot> - a folder, where zpm will look for ObjectScript to import. 

In this case we tell to look for ObjectScript in /src folder:

<SourcesRoot>src</SourcesRoot>

<Resource Name> - elements of ObjectScript to import. This could be packages, classes, includes, globals, dfi, etc. 

The structure under SourceRoot folder should be the following:

/cls - all the ObjectScript classes in Folder=Package, Class=file.cls form. Subpackages are subfolders

/inc - all the include files in file.inc form.

/mac - all the mac routines. 

/int - all the "intermediate" routines (AKA "other" code, the result of a compilation of mac code, or ObjectScirpt without classes and macro).

/gbl - all the globals in xml form of export.

/dfi - all the DFI files in xml form of export. Each pivot comes in pivot.dfi file, each dashboard comes in dashboard.dfi file.

E.g. here we import the ObjectScript page. This will tell to ZPM to look for /src/cls/ObjectScript folder and import all the classes from it:

<Resource Name="ObjectScript.PKG"/>

So! To prepare your solution for packaging put ObjectScript classes into some folder of your repository inside /cls folder and place all packages and classes in package=folder, class=file.cls form.

If you store classes in your repo differently and don't want a manual work to prepare the proper folder structure for ObjectScript there are plenty of tools which do the work: Atelier and VSCode ObjectScript export classes this way, also there is isc-dev utility which exports all the artifacts from namespace ready for packaging.

Packaging mac routines

This is very similar to classes. Just put routines under /mac folder. Example.

<?xml version="1.0" encoding="UTF-8"?>
<Export generator="Cache" version="25">
  <Document name="DeepSeeButtons.MODULE">
    <Module>
      <Name>DeepSeeButtons</Name>
      <Version>0.1.7</Version>
      <Packaging>module</Packaging>
      <SourcesRoot>src</SourcesRoot>
      <Resource Name="DeepSeeButtons.mac"/>
    </Module>
  </Document>
</Export>

Some other elements

There are also optional elements like:
<Author>

Which could contain <Organization> and <CopyrightDate> elements.

Example:

<Author>
        <Organization>InterSystems</Organization>
        <CopyrightDate>2019</CopyrightDate>
      </Author>

 

Packaging CSP/Web applications

ZPM can deploy web applications too.

To make it work introduce CSPApplication element with the clauses of CSP Application parameters. 

For example, take a look on DeepSeeWeb module.xml CSPApplication tag:

<CSPApplication
        Url="/dsw"
        Path="/build"
        Directory="{$cspdir}/dsw"
        ServeFiles="1"
        Recurse="1"
        CookiePath="/dsw"
       />

This setting will create a Web application with the name /dsw and will copy all the files from /build folder of the repository into {$cspdir}/dsw folder which is a folder under IRIS csp directory.

REST API application

If this is a REST-API application the CSPApplication element will contain dispatch class and could look like the MDX2JSON module.xml:

<CSPApplication
    Path="/MDX2JSON"
    Url="/MDX2JSON"
    CookiePath="/MDX2JSON/"
    PasswordAuthEnabled="1"
    UnauthenticatedEnabled="1"
    DispatchClass="MDX2JSON.REST"
    />

Dependencies

Your module could expect the presence of another module installed on the target system. This could be described by <Dependencies> element inside <Document> element which could contain several <ModuleReference> elements each of which has <Name> and <Version> and which state what other modules with what version should be installed before your one. This will cause ZPM to check, whether modules are installed and if not perform the installation.

Here is an example of dependency DSW module on MDX2JSON module:

<Dependencies>
        <ModuleReference>
          <Name>MDX2JSON</Name>
          <Version>2.2.0</Version>
        </ModuleReference>
      </Dependencies>

Another example where ThirdPartyPortlets depends on Samples BI(holefoods):

<Dependencies>
        <ModuleReference>
          <Name>holefoods</Name>
          <Version>0.1.0</Version>
        </ModuleReference>
      </Dependencies>

There are also options to run your arbitrary code to set up the data, environment and we will talk about it in the next articles.

How to build your own package

Ok! Once you have a module.xml you can try to build the package and test if the module.xml structure is accurate.

You may test via zpm client. Install zpm on an IRIS system and load the package code with load command:

zpm: NAMESPACE>load path-to-the-project

The path points to the folder which contains the resources for the package and has module.xml in the root folder. 

E.g. you can test the package building this project. Check out it and build a container with docker-compose-zpm.yml.

Open terminal in SAMPLES namespace and call ZPM:

zpm: SAMPLES>

zpm: SAMPLES>load /iris/app

[samples-objectscript]  Reload START
[samples-objectscript]  Reload SUCCESS
[samples-objectscript]  Module object refreshed.
[samples-objectscript]  Validate START
[samples-objectscript]  Validate SUCCESS
[samples-objectscript]  Compile START
[samples-objectscript]  Compile SUCCESS
[samples-objectscript]  Activate START
[samples-objectscript]  Configure START
[samples-objectscript]  Configure SUCCESS
[samples-objectscript]  Activate SUCCESS

The path is "/iris/app" cause we tell in docker-compose-zpm.yml that we map the root of the project to /iris/app folder in the container. So we can use this path to tell zpm where to load the project from.


So! The load performed successfully. And this means that module.xml could be used to submit a package to the developers' community repository.

Now you know how to make a proper module.xml for your application. 

How to submit the application to InterSystems Community repository

As for today there two requirements:

1. Your application should be listed on Open Exchange

2. Request me in Direct Message or in comments to this post if you want your application to be submitted to the Community Package manager repository.

And you should have a module.xml working!)


Continue reading with the next part Article