Skip to main content

Project compilation context

A compilation produces one Morphir package from a set of source documents. A workspace can contain several projects; each selected project contributes its own package, source root, module exposure, and compile task result. Multiple modules in one package do not imply cross-package dependency compilation.

Configuration and command-line precedence​

Select the project before applying command-line overrides. Configuration keeps its existing layer order: defaults, system, global user, workspace, selected member, adjacent user overrides, environment. Explicit command-line values win over the corresponding effective settings.

--project accepts an exact project name or a declared workspace-relative member path. . selects a project declared at the workspace root. Unknown or ambiguous names are errors. An explicit selection also works when invoked from a sibling member. Without it, configuration discovery selects the current member, then the configured default member or sole member. A workspace without an unambiguous project requires a selection.

InputResolution
--configSelects the configuration entry point; it does not change the working directory.
--projectSelects the member before configuration merging.
project.source_directoryRelative to the selected project root.
--inputReplaces the configured source input; relative to the invocation directory.
--package-nameReplaces the emitted package name; does not relocate project output.
--languageReplaces the configured frontend language.
--ir-versionReplaces ir.format_version for project compilation; v4 is the default.
--out-dirOverrides MORPHIR_OUT_DIR, then workspace.out_dir, then the default .morphir/out.
--outputInstalls a copy of the task product at the supplied location.

Scalar options replace their corresponding settings. They do not append paths or implicitly select another project. TOML and YAML represent the same nested configuration model. Legacy morphir.json continues to normalize name, sourceDirectory, and exposedModules into that model. An omitted project version defaults to 0.1.0, as in legacy normalization; opening a discovered project does not require adding a version just to read its model.

A compile of selected files (--input naming files) is an isolated compilation. The provider that declares the files' suffix synthesizes the project through workspace discovery: it names the package, unless --package-name or a loaded manifest supplies the name, and exposes every selected module. A surrounding project's exposure list does not add unsubmitted modules to that request, and an ambient manifest is read only when --config or --project asks for it. Project-mode compilation, including Python, forwards the configured exposure list for its complete source set.

Project compilation requests the selected IR version when resolving and invoking the frontend. A provider must advertise that version, and both its result and embedded document header must match the request. V3 supports JSON/YAML single-file storage; v4 also supports document trees. Task records retain the emitted version, so generation and workspace model loading read the correct transport. A compile of selected files negotiates the version with its provider too: it requests --ir-version when given and otherwise the oldest release the provider serves, so it keeps the classic IR single-file compiles have always written while a newer release stays one flag away.

Source identity across MEP hosts​

CompileRequest.sources is the complete compilation unit. Its documents field supplies document contents; resolving an identity does not authorize filesystem or network access. sources.root is the standard optional MEP source root, shared by CLI and UI callers, with these rules:

  • Use an absolute hierarchical URI for the source root. Absolute native paths remain accepted for compatibility; hosts should emit file URIs for local files.
  • Relative document paths retain their directory segments.
  • Absolute document URIs must be beneath the source root, with matching URI scheme and authority. Root matching respects path-segment boundaries.
  • Multiple documents containing any absolute URI require a source root. A single absolute document without a root retains basename identity for compatibility with existing single-document callers.
  • Query strings and fragments are diagnostic metadata, not module identity. Decode path segments once. Reject dot segments, encoded separators, invalid UTF-8, empty file paths, outside-root paths, and duplicate document identities.
  • Preserve the original URI in diagnostics. Frontends map validated relative source paths to language-specific module names and reject name collisions.

The Rust SDK owns validation through CompileRequest::source_paths() and the validated SourceRoot and SourcePath types. Keeping the root in the same SourceSet as its documents prevents document replacement or combination from silently changing module identities.

On the wire the root has a legacy second spelling: options.sourceRootUri alongside a top-level documents array, which CLI releases up to 0.4.0-beta.5 sent to process and WASM providers. The CLI now sends sources to every provider. A frontend built on the SDK never sees the difference — the decoder normalizes both into SourceSet — so this concerns hosts and released artifacts, not frontend authors.

Python derives domain/models.py as domain.models; a nested __init__.py represents its package module according to the Python frontend's documented rules.

One root per compilation gives stable module identity across local paths, editor documents, and generated source round trips. Computing a common ancestor would make module names change when documents are added or removed. Explicit per-document module IDs or several source roots would need additional collision and import-resolution rules; neither is inferred by this contract.

Public and private modules​

package.exposedModules is optional. Omission exposes every compiled module for ad hoc callers. An explicit empty array exposes none. A nonempty array names exactly the public modules; all other compiled modules are private. Unknown module names are errors. Configuration forwards its exposure list without replacing it with an empty array.

Private modules remain in the package definition and may be referenced by sibling modules. They are excluded from its public module specification. Python generation emits both public and private modules so internal references remain usable. Python does not enforce Morphir access control at runtime: retain the exposure configuration when recompiling generated files. Module access does not imply support for private types, values, or constructors.

The SDK's Rust field becomes Option<Vec<String>>. Existing callers that intended an empty public interface must send Some(vec![]); callers intending all modules public use None. This is an API change within the current 0.x SDK. Older Python releases reject partial exposure; use an extension build containing private-module support.

Loading the compile result​

Connected workspace providers resolve the selected project's output using the same configuration and out-root precedence as the CLI. They read <out>/<member>/compile.json and the IR descriptor inside compile.dest, including JSON/YAML single files and document trees. The task record carries the artifact's format, layout, and version; current configuration does not reinterpret an earlier successful artifact.

A present invalid or tombstoned record is an error. Only an absent record permits the legacy <project>/morphir-ir.json fallback. Readers hold the compile task's shared lock while reading its record and artifact. Artifact paths stay confined to their granted output directory; record paths cannot grant new access. Document trees are reconstructed as a single IR value for the existing UI RPC.

This contract covers connected CLI workspace providers. Browser-local workspace loading requires a corresponding browser implementation and an explicit handle grant if the configured output lies outside the opened directory.