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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- #1117: Add `sync` command for incremental loading of changed files in dev-mode modules. Detects modified files since last sync using SHA-1 hash and recompiles only what is stale. Supports `-delete` for processing removed files and `-test` for running changed test-phase unit tests.
- #106: A module can now specify `<SystemRequirements SYSNamespace="true"/>` to prevent installation in non-%SYS namespaces.
- #1158: Installing deployed code modules will automatically filter for the current IRIS version

### Changed
- Minimum supported Python version is now 3.9
- #1158: `search` and `install` no longer list deployed modules that were not built for the current IRIS platform. Use `search <module> -versions` to see all versions and their supported platforms.

### Fixed
- Performance: Studio project creation on package load in dev mode is now 80% faster.
- #994: Prevent crash in `zn` command when target namespace lacks IPM mappings
- #987: Fixed issue where installing dependencies with ORAS would fail due to failure to properly get the manifest

## [0.10.9] - 2026-08-05

Expand Down
21 changes: 15 additions & 6 deletions src/cls/IPM/Lifecycle/Base.cls
Original file line number Diff line number Diff line change
Expand Up @@ -1120,13 +1120,22 @@ Method %Compile(ByRef pParams) As %Status

// Just copy the first level of subscripts (resource names), not all the gory details.
set tOneResourceKey = ""
set tAnyToCompile = 0
for {
set tOneResourceKey = $order(tOneResourceList(tOneResourceKey))
quit:(tOneResourceKey="")
if (('tDevMode) && ($get(tOneResourceList(tOneResourceKey, "Deploy"))) && ('isKitBuild) && (..Module.Deployed)) {
continue
}
set tCompileFlagMap(tFlags,tResource.Name,tOneResourceKey) = ""
set tAnyToCompile = 1
}

// Everything under this resource is already deployed, so no compile pass will
// ever claim it. Leaving it in tCompilableMap would stall the loop below, which
// only clears resources it compiles.
if 'tAnyToCompile {
kill tCompilableMap(tResource.Name)
}
}
}
Expand Down Expand Up @@ -1201,8 +1210,9 @@ Method %Compile(ByRef pParams) As %Status
}
}

// Cycle detection
if ('tCompiledSomething && '..HasDeployedResources) {
// Cycle detection. Anything not compilable has already been removed above, so a
// resource still waiting its turn here is waiting on itself.
if 'tCompiledSomething {
set tFailedResources = ""
set tResource = ""
for {
Expand Down Expand Up @@ -1713,8 +1723,8 @@ Method %Document(ByRef pParams) As %Status
set tSC = ..Module.BuildDependencyGraph(.tDependencyGraph)
$$$ThrowOnError(tSC)

// tDependencyGraph format:
// tDependencyGraph(<module name>) = $ListBuild(<depth>, <server>, <version>)
// tDependencyGraph format: see %IPM.Storage.Module:BuildDependencyGraph for the node
// layout. Only <depth> and <server> are used here.
// tDependencyGraph(<module name>,<dependent module name>_" "_<version string>) = <required version expression>
// <server> being empty means it's already installed.
// All these modules need to already be installed.
Expand Down Expand Up @@ -1867,8 +1877,7 @@ Method %MakeDeployed(ByRef pParams) As %Status
if '$isobject(..Module.SystemRequirements) {
set ..Module.SystemRequirements = ##class(%IPM.Storage.SystemRequirements).%New()
}
set tSystemVersion = $system.Version.GetMajor() _ "." _ $system.Version.GetMinor()
set ..Module.SystemRequirements.VersionString = tSystemVersion
set ..Module.SystemRequirements.VersionString = $$$CurrentPlatformVersion
$$$ThrowOnError(..Module.%Save())
if $data(tDeployClassArray) {
if (tVerbose) {
Expand Down
32 changes: 23 additions & 9 deletions src/cls/IPM/Main.cls
Original file line number Diff line number Diff line change
Expand Up @@ -2261,7 +2261,7 @@ ClassMethod ShowModulesForRepository(
} elseif pSearchString'="" {
set where=" WHERE name = ?"
}
set tQuery = "SELECT Name,Version,Repository,Description,Origin,AllVersions FROM %IPM_Utils.Module_GetModuleList(?,?) "_where
set tQuery = "SELECT Name,Version,Repository,Description,Origin,AllVersions,PlatformVersions FROM %IPM_Utils.Module_GetModuleList(?,?) "_where
if (pSearchString = "") {
set tRes = ##class(%SQL.Statement).%ExecDirect(, tQuery, pRepoName, pShowAllVersions)
} else {
Expand All @@ -2279,6 +2279,7 @@ ClassMethod ShowModulesForRepository(
set list = list + 1
set list(list) = $listbuild(name, tRes.Version)
set list(list, "Origin") = tRes.Origin
set list(list, "PlatformVersions") = tRes.PlatformVersions
if pShowDesc {
set list(list, "Description") = tRes.Description
}
Expand Down Expand Up @@ -2552,7 +2553,7 @@ ClassMethod Install(
set tSearchCriteria.Name = $$$lcase(tModuleName)
set tSearchCriteria.VersionExpression = tVersion
set tSearchCriteria.Keywords = tKeywords
$$$ThrowOnError(##class(%IPM.Repo.Utils).SearchRepositoriesForModule(tSearchCriteria,.tResults))
$$$ThrowOnError(##class(%IPM.Repo.Utils).SearchRepositoriesForModule(tSearchCriteria,.tResults,,.tPlatformFiltered))

if (tResults.Count() > 0) {
set tResult = ""
Expand Down Expand Up @@ -2586,19 +2587,31 @@ ClassMethod Install(
if '$get(tParams("Update"),0) {
set tParams("cmd") = "install"
set tParams("Install") = 1
}
if tResult.Deployed {
set platformVersion = $system.Version.GetMajor() _ "." _$system.Version.GetMinor()
set tResult.PlatformVersion = platformVersion
if ('tResult.PlatformVersions.Find(platformVersion)) {
$$$ThrowStatus($$$ERROR($$$GeneralError, "Deployed package '" _ tModuleName _ "' " _ tResult.VersionString _ " not supported on this platform " _ platformVersion _ "."))
}
}
$$$ThrowOnError(log.SetSource(tResult.ServerName))
$$$ThrowOnError(log.SetVersion(tResult.Version))
$$$ThrowOnError(##class(%IPM.Utils.Module).LoadQualifiedReference(tResult, .tParams, , log))
}
} else {
// The module may have matched but been excluded for being deployed for other platforms
// only, which deserves a better message than "not found".
// Only a deployed module with platforms that don't include this one is ever filtered,
// so there is always something to report here.
if $isobject(tPlatformFiltered) && (tPlatformFiltered.Count() > 0) {
// Report the newest filtered version: the one an unversioned install resolves to.
// A pinned install filters only its own version, so this names that version too.
#dim tFiltered As %IPM.Storage.ModuleInfo
#dim tCandidate As %IPM.Storage.ModuleInfo
set tFiltered = tPlatformFiltered.GetAt(1)
for tIdx = 2:1:tPlatformFiltered.Count() {
set tCandidate = tPlatformFiltered.GetAt(tIdx)
if tCandidate.Version.Follows(tFiltered.Version) {
set tFiltered = tCandidate
}
}
set tMsg = "'" _ tFiltered.Name _ "' " _ tFiltered.VersionString _ " is not available for platform " _ $$$CurrentPlatformVersion _ ". Available platforms: " _ tFiltered.FormatPlatformVersions() _ ". Run 'search " _ tFiltered.Name _ " -versions' to see all versions."
$$$ThrowStatus($$$ERROR($$$GeneralError, tMsg))
}
set tPrefix = ""
if (tModuleName '= "") {
if (tVersion '= "") {
Expand Down Expand Up @@ -4225,6 +4238,7 @@ ClassMethod DisplayModules(
$listbuild("Author.Person", $$$Yellow, "Author"),
$listbuild("Origin", $$$Default),
$listbuild("AllVersions", $$$Default, "Versions"),
$listbuild("PlatformVersions", $$$Default, "Platforms"),
$listbuild("Repository", $$$Default),
$listbuild("Installed In", $$$Default)
)
Expand Down
22 changes: 20 additions & 2 deletions src/cls/IPM/Repo/Manager.cls
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
Include %IPM.Common

Class %IPM.Repo.Manager Extends %IPM.General.Singleton
{

Expand Down Expand Up @@ -47,13 +49,18 @@ Method CheckServiceCache(
}

/// Returns a list of all resolved module references matching the search criteria from configured repositories.
/// pPlatformFilteredResults receives the deployed results that matched the criteria but were excluded
/// for having no artifact for this platform, so callers can distinguish "no such module" from
/// "not built for this platform" without searching again.
Method SearchRepositoriesForModule(
pSearchCriteria As %IPM.Repo.SearchCriteria,
Output pResults As %Library.ListOfObjects(ELEMENTTYPE="%IPM.Storage.QualifiedModuleInfo"),
pForceRemoteRepo As %Boolean = 0) As %Status
pForceRemoteRepo As %Boolean = 0,
Output pPlatformFilteredResults As %Library.ListOfObjects(ELEMENTTYPE="%IPM.Storage.ModuleInfo")) As %Status
{
set tSC = $$$OK
set pResults = ##class(%Library.ListOfObjects).%New()
set pPlatformFilteredResults = ##class(%Library.ListOfObjects).%New()
try {
set registry = pSearchCriteria.Registry
set pSearchCriteria.Name = $$$lcase(pSearchCriteria.Name)
Expand All @@ -66,6 +73,7 @@ Method SearchRepositoriesForModule(
set tSC = $$$ERROR($$$SQLCode,tRes.%SQLCODE,tRes.%Message)
quit
}
set currentPlatform = $$$CurrentPlatformVersion
while (tRes.%Next(.tSC)) {
if $$$ISERR(tSC) {
quit
Expand Down Expand Up @@ -101,7 +109,17 @@ Method SearchRepositoriesForModule(
set tServerResults = tClient.ListModules(tClientCriteria)
for i=1:1:tServerResults.Count() {
set item = tServerResults.GetAt(i)
#; Sorting versions
if 'item.IsCompatibleWithPlatform(currentPlatform) {
do pPlatformFilteredResults.Insert(item)
continue
}
// Record which platform's artifact this result refers to; callers need it to build
// the tag. Left empty for repositories that don't report platforms, and for
// artifacts that aren't platform-specific at all.
if item.Deployed && (item.PlatformVersions.Find(currentPlatform) > 0) {
set item.PlatformVersion = currentPlatform
}
// Sorting versions
set versions(+item.Version.Major, +item.Version.Minor, +item.Version.Patch, " "_item.Version.Patch_"_"_item.Version.Prerelease, repoID, tServer.Name) = item
}
}
Expand Down
64 changes: 57 additions & 7 deletions src/cls/IPM/Repo/Oras/PackageService.cls
Original file line number Diff line number Diff line change
Expand Up @@ -151,10 +151,7 @@ Method GetModule(
pModuleReference As %IPM.Storage.ModuleInfo,
Output AsArchive As %Boolean = 0) As %Stream.Object
{
set tag = $$$Semver2Tag(pModuleReference.VersionString)
if (pModuleReference.Deployed) {
set tag = tag _ $$$OrasTagPlatformSeparator _ pModuleReference.PlatformVersion
}
set tag = ..TagForReference(pModuleReference)
set status = ..Pull(..Location, pModuleReference.Name, tag, ..Namespace, ..Username, ..Password, ..Token, ..TokenAuthMethod, .stream)
$$$ThrowOnError(status)
#; module is pulled as a .tgz file
Expand All @@ -164,8 +161,31 @@ Method GetModule(

Method GetModuleManifest(pModuleReference As %IPM.Storage.ModuleInfo) As %Stream.Object
{
set name = pModuleReference.Name _ ":" _ $$$Semver2Tag(pModuleReference.VersionString)
return ..GetModuleXML(..Location, name, ..Namespace, ..Username, ..Password, ..Token, ..TokenAuthMethod)
set name = pModuleReference.Name _ ":" _ ..TagForReference(pModuleReference)
// GetModuleXML returns a string, but callers expect a stream per %IPM.Repo.IPackageService
set moduleXML = ..GetModuleXML(..Location, name, ..Namespace, ..Username, ..Password, ..Token, ..TokenAuthMethod)
if (moduleXML = "") {
$$$ThrowStatus($$$ERROR($$$GeneralError, "No manifest found for '" _ name _ "' in registry " _ ..Location))
}
set stream = ##class(%Stream.GlobalCharacter).%New()
$$$ThrowOnError(stream.Write(moduleXML))
$$$ThrowOnError(stream.Rewind())
return stream
}

/// Returns the registry tag holding the artifact for the given module reference.
/// A deployed module is tagged with a platform version suffix, since one version of it
/// is built separately for each supported platform.
ClassMethod TagForReference(moduleReference As %IPM.Storage.ModuleInfo) As %String [ Private ]
{
set tag = $$$Semver2Tag(moduleReference.VersionString)
if moduleReference.Deployed {
if (moduleReference.PlatformVersion = "") {
$$$ThrowStatus($$$ERROR($$$GeneralError, "Cannot locate deployed module '" _ moduleReference.Name _ "' " _ moduleReference.VersionString _ " without a platform version."))
}
set tag = tag _ $$$OrasTagPlatformSeparator _ moduleReference.PlatformVersion
}
return tag
}

Method HasModule(pModuleReference As %IPM.Storage.ModuleInfo) As %Boolean
Expand Down Expand Up @@ -213,6 +233,29 @@ ClassMethod AggregatePlatformVersions(
return ##class(%IPM.Utils.EmbeddedPython).FromPythonList(sortedTags)
}

/// Reorders versionsList so that versions with an artifact for currentPlatform come first, each
/// group keeping the newest-first order it arrived in. A caller that reports only the first version
/// therefore reports the newest installable one, falling back to the newest version overall when
/// the module has no artifact for this platform at all.
ClassMethod PreferCompatibleVersions(
versionsList As %List,
ByRef aggregatedPlatformVersion,
currentPlatform As %String) As %List
{
set compatible = "", incompatible = ""
set ptr = 0
while $listnext(versionsList, ptr, version) {
set platformVersions = $get(aggregatedPlatformVersion(version))
// An empty platform means the version is not platform-specific, so anything can use it.
if ($listfind(platformVersions, "") > 0) || ($listfind(platformVersions, currentPlatform) > 0) {
set compatible = compatible _ $listbuild(version)
} else {
set incompatible = incompatible _ $listbuild(version)
}
}
return compatible _ incompatible
}

/// <var>moduleList</var> is a list of objects of type <class>%IPM.Storage.ModuleInfo</class> to which this method adds entries
/// <var>name</var> is the name of the package for which we are enumerating versions
Method ListModulesFromTagString(
Expand All @@ -224,6 +267,11 @@ Method ListModulesFromTagString(
{
set allTagsString = ..GetAllTags(..Location, name, "", client)
set allVersionsList = ..AggregatePlatformVersions($listfromstring(allTagsString, ", "), .aggregatedPlatformVersion)
// The tri-states of AllVersions that report a single version should report one this platform
// can actually install. The empty state reports every version, so its order doesn't matter.
if (searchCriteria.AllVersions '= "") {
set allVersionsList = ..PreferCompatibleVersions(allVersionsList, .aggregatedPlatformVersion, $$$CurrentPlatformVersion)
}
set pointer = 0
while $listnext(allVersionsList,pointer,moduleVersion) {
// filter by version
Expand Down Expand Up @@ -300,7 +348,9 @@ Method ListModulesFromTagString(
set tModRef.Repository = artifactMetadata.ImageSource
set tModRef.Description = artifactMetadata.ImageDescription
set tModRef.Deployed = artifactMetadata.IPMDeployed
if (artifactMetadata.IPMDeployed '= "") {
// Skipped for AllVersions, which reports a single entry standing in for every version;
// these platforms belong to just one of them.
if (artifactMetadata.IPMDeployed '= "") && 'searchCriteria.AllVersions {
set pvPtr = 0
while $listnext(platformVersions, pvPtr, pv) {
$$$ThrowOnError(tModRef.PlatformVersions.Insert(pv))
Expand Down
3 changes: 3 additions & 0 deletions src/cls/IPM/Repo/Oras/PublishService.cls
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ Method PublishModule(pModule As %IPM.Repo.Remote.ModuleInfo) As %Status
set metadata.IPMModuleV1XML = moduleXML
do metadata.%JSONExportToString(.metaDataString)
if (pModule.Deployed) {
if (pModule.PlatformVersion = "") {
$$$ThrowStatus($$$ERROR($$$GeneralError, "Cannot publish deployed module '" _ pModule.Name _ "' without a platform version."))
}
set tag = tag _ $$$OrasTagPlatformSeparator _ pModule.PlatformVersion
}

Expand Down
2 changes: 1 addition & 1 deletion src/cls/IPM/Repo/Remote/PackageService.cls
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ Method GetModule(
set deployed = pModuleReference.Deployed

if deployed {
set platformVersion = $system.Version.GetMajor() _ "." _$system.Version.GetMinor()
set platformVersion = $$$CurrentPlatformVersion
}

set tSC = tRequest.Get($$$URLENCODE(tRequest.Location _ "packages/" _ name _ "/" _ version _ $select(deployed: "/" _ platformVersion, 1: "")))
Expand Down
9 changes: 6 additions & 3 deletions src/cls/IPM/Repo/Utils.cls
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,20 @@ Class %IPM.Repo.Utils
{

/// Returns a list of all resolved module references matching the search criteria from configured repositories.
/// See %IPM.Repo.Manager:SearchRepositoriesForModule for pPlatformFilteredResults.
ClassMethod SearchRepositoriesForModule(
pSearchCriteria As %IPM.Repo.SearchCriteria,
Output pResults As %Library.ListOfObjects(ELEMENTTYPE="%IPM.Storage.QualifiedModuleInfo"),
pForceRemoteRepo As %Boolean = 0) As %Status
pForceRemoteRepo As %Boolean = 0,
Output pPlatformFilteredResults As %Library.ListOfObjects(ELEMENTTYPE="%IPM.Storage.ModuleInfo")) As %Status
{
set tSC = $$$OK
set pResults = $$$NULLOREF
set pResults = ""
set pPlatformFilteredResults = ""
try {
set tManager = ##class(%IPM.Repo.Manager).%Get(.tSC)
$$$ThrowOnError(tSC)
set tSC = tManager.SearchRepositoriesForModule(pSearchCriteria,.pResults)
set tSC = tManager.SearchRepositoriesForModule(pSearchCriteria,.pResults,,.pPlatformFilteredResults)
} catch e {
set tSC = e.AsStatus()
}
Expand Down
7 changes: 7 additions & 0 deletions src/cls/IPM/ResourceProcessor/Default/Document.cls
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,13 @@ Method OnPhase(
write !, "Loading deployed resource from Studio project: "_..ResourceReference.Name
}
set ..CompileFromProject = 1
// Mark deployed so Compile skips source compilation of Deploy=true
// resources. Persisted because the module is reopened from the
// database between here and Compile.
if '..ResourceReference.Module.Deployed {
set ..ResourceReference.Module.Deployed = 1
$$$ThrowOnError(..ResourceReference.Module.%Save())
}
if '$data($$$DeployedProjectInstalled(tDeployedProjectPath)) {
set sc = ##class(%Studio.Project).InstallFromFile(tDeployedProjectPath, "<none>")
if $$$ISERR(sc) {
Expand Down
Loading
Loading