Getting Started
Install the Morphir toolchain, compile your first model and generate code from it
Start here
Your first model with morphirup takes you from nothing to
generated code: install morphirup, set up the toolchain, compile a small F# model and generate F# from it. It works
on Windows and on Macs with Apple silicon, and needs no .NET.
Installation lists every way to install morphirup and the
Morphir CLI.
Next
When something goes wrong, see Troubleshooting.
1 - Your first model with morphirup
Install morphirup, set up the toolchain, compile a small F# model, generate F# from it, then change a rule and see the result
In this walkthrough you install morphirup, set up the Morphir toolchain, compile a small F# model, and generate F#
from it. Then you change one rule and see the change in the output. You don’t need .NET installed.
| Platform | Supported |
|---|
| Windows x64 and ARM64 | Yes |
| macOS on Apple silicon | Yes |
| Linux x64 and ARM64 | morphirup installs, but setup doesn’t support Linux yet |
Intel Macs are not supported. If something goes wrong, see Troubleshooting.
What morphirup is
morphirup installs the Morphir command line, morphir, together with the F# support it needs: a frontend that
turns F# into Morphir IR, and a backend that turns Morphir IR into F#. morphirup run runs them for you.
1. Install morphirup
In PowerShell:
irm https://raw.githubusercontent.com/finos/morphir-dotnet/main/scripts/install-morphirup.ps1 | iex
In a terminal:
curl -fsSL https://raw.githubusercontent.com/finos/morphir-dotnet/main/scripts/install-morphirup.sh | sh
The script downloads the newest morphirup, checks it against the release’s checksums, and installs it:
morphirup-install: finding the newest release with morphirup-win-arm64.exe
morphirup-install: downloading morphirup-win-arm64.exe 0.4.0-alpha.5
morphirup-install: SHA-256 verified: 6bb59736f765a9f2cbdf7262a1f07838c15c6025927f10f7642f4307a8d237f3
morphirup-install: installed ...\morphirup.exe
morphirup-install: morphirup --version: 0.4.0-alpha.5+3abb0de0a9de5a321e109db5f2eef3afcdd429ea
Your version may be newer. Open a new terminal, then check that morphirup is found:
0.4.0-alpha.5+3abb0de0a9de5a321e109db5f2eef3afcdd429ea
If the terminal says morphirup is not found, see
morphirup is not found.
Installation says where the script puts
morphirup, lists its options, and has the other ways to install it.
2. Get the example project
The example has two files. Download them into a new folder, and run every later command in that folder:
New-Item -ItemType Directory -Force morphirup-fsharp\src | Out-Null
Set-Location morphirup-fsharp
$raw = 'https://raw.githubusercontent.com/finos/morphir-dotnet/main/examples/morphirup-fsharp'
Invoke-WebRequest "$raw/morphir.toml" -OutFile morphir.toml
Invoke-WebRequest "$raw/src/Pricing.fs" -OutFile src\Pricing.fs
mkdir -p morphirup-fsharp/src && cd morphirup-fsharp
raw=https://raw.githubusercontent.com/finos/morphir-dotnet/main/examples/morphirup-fsharp
curl -fsSL -o morphir.toml "$raw/morphir.toml"
curl -fsSL -o src/Pricing.fs "$raw/src/Pricing.fs"
If you have cloned the repository, the same files are in examples/morphirup-fsharp.
See what setup would install, without installing anything:
morphirup setup --dry-run
morphirup: release-checksum delivery, NOT publisher-authenticated.
{
...
"command": "setup",
"status": "planned",
...
}
Run morphirup setup --yes to install and select this toolchain.
Then install it:
{
...
"command": "setup",
"status": "installed",
...
}
morphirup: release-checksum delivery, NOT publisher-authenticated.
Setup downloads the Morphir CLI, the F# frontend and the F# backend from the same release as morphirup. It checks
every file against the release’s checksums, tries the tools out, and then makes them the ones morphirup run uses.
It took about 35 seconds here. This is the only step that needs the network.
setup, show and run all print the line release-checksum delivery, NOT publisher-authenticated. It means the files
matched the checksums the release published; it doesn’t mean they were signed.
Troubleshooting says more.
Check what is installed:
{
...
"command": "show",
"status": "shown",
...
}
morphirup: release-checksum delivery, NOT publisher-authenticated.
"status": "shown" means a toolchain is set up and selected. Each setup --yes installs a new copy and selects it;
morphirup select switches back to an earlier one. See the
morphirup reference for both.
4. Read the model
morphir.toml names the project:
[project]
name = "acme/demo"
source_directory = "src"
[frontend]
language = "fsharp"
[ir]
format_version = 4
The package is acme/demo, its source is F# in the src folder, and Morphir IR is written in format 4.
src/Pricing.fs is the model:
namespace Acme.Demo
module Pricing =
type Car = { Plate: string; Seats: int }
let seats (car: Car) : int = car.Seats
A model is ordinary F#. This one has a type and a rule. A Car has a number plate and a number of seats. The rule
seats says how many seats a car has to sell.
5. Compile the model
morphirup: release-checksum delivery, NOT publisher-authenticated.
compile succeeded: 1 module(s), IR version 4, ...\morphirup-fsharp\.morphir\out\compile.dest\morphir-ir.json; no diagnostics
...\morphirup-fsharp\.morphir\out\compile.dest\morphir-ir.json
Everything after -- goes to the Morphir CLI. It read morphir.toml, compiled src/Pricing.fs, and wrote the model
as Morphir IR to .morphir/out/compile.dest/morphir-ir.json.
Morphir IR is your model as data that any Morphir tool can read. Open the file and you’ll find, among the details:
{
"formatVersion": 4,
"distribution": {
"Library": {
"packageName": "acme/demo",
...
"modules": {
"pricing": {
...
"types": {
"car": {
...
"fields": {
"plate": {
...
"fqname": "morphir/SDK:string#string"
...
"seats": {
...
"fqname": "morphir/SDK:basics#int"
...
"values": {
"seats": {
...
"body": {
"Field": {
...
"name": "seats"
...
}
The package acme/demo has one module, pricing. It has the type car, a record with a string plate and an int
seats, and the rule seats, whose body reads the field seats. Morphir writes names without the F# capitals.
6. Generate F#
morphirup run -- generate --target fsharp --output generated
Get-Content generated\Pricing.fs
morphirup run -- generate --target fsharp --output generated
cat generated/Pricing.fs
morphirup: release-checksum delivery, NOT publisher-authenticated.
Pricing.fs
module Acme.Demo.Pricing
type Car = { plate: string; seats: int }
let seats (car: Car) : int = (car).seats
generate read the IR from the last compile and wrote generated/Pricing.fs. This F# comes from the IR, not from
your source file, which is why the field names are in lower case.
7. Change a rule
Say the driver’s seat is not for sale. Keep a copy of the current output, so you can compare:
Copy-Item generated generated-before -Recurse
cp -R generated generated-before
Open src/Pricing.fs in an editor and change the last line:
let seats (car: Car) : int = car.Seats - 1
Compile, generate and compare:
morphirup run -- compile
morphirup run -- generate --target fsharp --output generated
Compare-Object (Get-Content generated-before\Pricing.fs) (Get-Content generated\Pricing.fs)
morphirup run -- compile
morphirup run -- generate --target fsharp --output generated
diff generated-before/Pricing.fs generated/Pricing.fs
On Windows the comparison ends with:
InputObject SideIndicator
----------- -------------
let seats (car: Car) : int = ((-) ((car).seats) (1)) =>
let seats (car: Car) : int = (car).seats <=
<= marks the old line and => the new one. The generated F# now subtracts 1 from the seats. It writes
car.seats - 1 as a call of the operator (-), which means the same. That’s the whole loop: change the model,
compile, generate.
8. Work offline
Setup needed the network. Compile and generate don’t. To see this, point the terminal at a proxy that doesn’t exist,
so that every download fails:
$env:HTTPS_PROXY = 'http://127.0.0.1:9'
$env:HTTP_PROXY = 'http://127.0.0.1:9'
morphirup setup --dry-run
morphirup run -- compile
morphirup run -- generate --target fsharp --output generated
Remove-Item Env:HTTPS_PROXY, Env:HTTP_PROXY
export HTTPS_PROXY=http://127.0.0.1:9 HTTP_PROXY=http://127.0.0.1:9
morphirup setup --dry-run
morphirup run -- compile
morphirup run -- generate --target fsharp --output generated
unset HTTPS_PROXY HTTP_PROXY
setup --dry-run fails because it can’t reach the release:
"status": "refused",
...
"code": "acquisition.refused",
"message": "The bounded release transfer was refused by transport or byte-integrity checks."
Compile and generate still succeed, with the same output as before.
Next steps
2 - Installation
Install morphirup, the Morphir toolchain manager, then the older routes to the morphir CLI
Start with morphirup, the Morphir toolchain manager. It is one file and needs nothing else installed. It sets up
the Morphir toolchain for your project, which then compiles and generates. When it is installed, follow
Your first model.
Install morphirup
On macOS (Apple silicon) and Linux (x64, ARM64):
curl -fsSL https://raw.githubusercontent.com/finos/morphir-dotnet/main/scripts/install-morphirup.sh | sh
On Windows (x64, ARM64), in PowerShell:
irm https://raw.githubusercontent.com/finos/morphir-dotnet/main/scripts/install-morphirup.ps1 | iex
The script installs the newest release for your machine and verifies the download first:
- On macOS and Linux it installs
~/.local/bin/morphirup. If that folder is not on your PATH, the script prints
the line to add to your shell profile. - On Windows it installs
%LOCALAPPDATA%\Programs\morphirup\morphirup.exe and adds the folder to your user PATH.
Open a new terminal and check that it runs:
It prints the version you installed.
Script options
| To | macOS and Linux | Windows |
|---|
| Install a given release | --version <version> | -Version <version> |
| Install into another folder | --install-dir <dir> | -InstallDir <dir> |
Leave PATH alone | The script never changes it | -NoModifyPath |
The versions are on the releases page. To pass an option:
curl -fsSL https://raw.githubusercontent.com/finos/morphir-dotnet/main/scripts/install-morphirup.sh | sh -s -- --version <version>
& ([scriptblock]::Create((irm https://raw.githubusercontent.com/finos/morphir-dotnet/main/scripts/install-morphirup.ps1))) -Version <version>
The environment variables MORPHIRUP_VERSION and MORPHIRUP_INSTALL_DIR work too, on every platform.
Behind a proxy or firewall
The scripts need to reach raw.githubusercontent.com (for the script itself), github.com and
release-assets.githubusercontent.com. Without --version, they also ask api.github.com for the newest release,
and read the release list on github.com if that host is blocked.
- Proxy. On macOS and Linux, set
HTTPS_PROXY (and NO_PROXY for internal hosts), for example
export HTTPS_PROXY=http://proxy.example.com:8080. On Windows, the script uses the Windows proxy settings, or
HTTPS_PROXY when you set it, and signs in to the proxy as you. - TLS inspection. On macOS and Linux, point
CURL_CA_BUNDLE (or SSL_CERT_FILE for wget) at your company’s CA
bundle. On Windows, the company’s root certificate must be in the Windows certificate store, which IT usually
manages. - Mirror. If your company mirrors GitHub release downloads, set
MORPHIRUP_DOWNLOAD_URL to the mirror of
https://github.com/finos/morphir-dotnet/releases/download. The mirror must serve HTTPS and keep the same layout,
<url>/v<version>/<file>. Pass --version / -Version if the mirror is all you can reach.
With mise
mise use -g 'github:finos/morphir-dotnet[matching=morphirup,version_prefix=v,prerelease=true]'
morphirup --version
To install a given release, add @<version> at the end, inside the quotes.
Download it yourself
From the releases page, download the file for your machine and
the release’s checksum file morphirup-v<version>.sha256:
| Machine | File |
|---|
| macOS, Apple silicon | morphirup-osx-arm64 |
| Linux x64 | morphirup-linux-x64 |
| Linux ARM64 | morphirup-linux-arm64 |
| Windows x64 | morphirup-win-x64.exe |
| Windows ARM64 | morphirup-win-arm64.exe |
Check the file before you run it. On macOS and Linux, in the download folder (on Linux, sha256sum -c - works too):
f=morphirup-osx-arm64; v='<version>'
grep " $f\$" "morphirup-v$v.sha256" | shasum -a 256 -c -
chmod +x "$f" && mkdir -p ~/.local/bin && mv "$f" ~/.local/bin/morphirup
It prints morphirup-osx-arm64: OK. If it prints anything else, delete the file.
On Windows, in PowerShell:
$f = 'morphirup-win-x64.exe'; $v = '<version>'
$expected = (Select-String -Path "morphirup-v$v.sha256" -Pattern " $f$").Line.Split(' ')[0]
(Get-FileHash $f -Algorithm SHA256).Hash -eq $expected
It prints True. Rename the file to morphirup.exe and move it into a folder on your PATH.
| Platform | Install morphirup | Set up, compile and generate |
|---|
| macOS on Apple silicon | Yes | Yes |
| Windows x64 and ARM64 | Yes | Yes |
| Linux x64 and ARM64 | Yes | Not yet: morphirup setup does not support Linux yet |
| macOS on Intel | No | No |
On Linux, morphirup needs glibc 2.35 or later (Ubuntu 22.04, Debian 12 or later). Alpine and other musl
distributions are not supported. Until setup supports Linux, use the standalone morphir CLI below.
The morphirup reference covers the release files and how they are checked.
If something goes wrong, see Troubleshooting.
Other ways to install the standalone morphir CLI
These routes install the morphir command line itself, without morphirup.
Requirements
What morphir-dotnet needs from the machine depends on the offering. A .NET runtime is what runs a managed program; a
.NET SDK is the larger download that also builds and installs one. Each offering states its needs in
morphir extension list --json (deployment and prerequisites, see
extension list):
| Offering | To run it | To install it | Released |
|---|
Native AOT morphir | Nothing from .NET | Nothing | Not yet as this offering |
Self-contained morphir (carries its own .NET runtime) | Nothing from .NET | Nothing | Not yet |
Framework-dependent morphir | The .NET runtime Microsoft.NETCore.App 11.x | Nothing | Not yet |
dotnet-morphir (Morphir.Tool dotnet tool) | The .NET runtime Microsoft.NETCore.App 11.x | A .NET SDK 11.0.100 or later, for dotnet tool install | Yes, on NuGet |
The morphir-<rid> executables that releases attach today are a trimmed single-file build that carries its runtime,
so they need nothing from .NET either; no named release profile produces them, so they report no deployment. Building
from source needs the .NET SDK that global.json names.
Install the Morphir CLI as a global .NET tool. The command is dotnet-morphir:
dotnet tool install --global Morphir.Tool --prerelease
Standalone Executable
The standalone morphir is one file that carries its .NET runtime, so it needs no .NET SDK or runtime. It is a
managed, trimmed, self-contained single-file build without an F# frontend, not a Native AOT executable. Download
morphir-linux-x64, morphir-linux-arm64, morphir-osx-arm64, morphir-win-x64.exe or morphir-win-arm64.exe
from the releases page. The Windows ARM64 file is built on a native
Windows 11 ARM64 runner; releases before the first one that lists it have no win-arm64 file. To compile
F# with it, also install the F# frontend extension:
dotnet tool install --global Morphir.Frontend.FSharp.Extension --prerelease
The extension tool needs the .NET runtime. On a machine without .NET, use the
self-contained frontend folder instead. Releases do not publish it yet; see
Compile without .NET installed
for how to build it and what it supports: compiling supported F# source files
to Morphir IR, not building .fsproj projects. The folder is not trimmed and
is not a single file. For win-arm64 it is about 120 MB, and its zip archive
about 47 MB. The exact file count and sizes of a build are in the log of the
build target PublishFSharpFrontendProcess, which prints them for the folder
and the archive.
| Platform | Self-contained F# frontend |
|---|
Windows ARM64 (win-arm64) | Checked on Windows 11 ARM64 with the machine’s .NET hidden from the frontend (no dotnet on PATH, no DOTNET_ROOT, no global install); not run on a machine with no .NET at all |
Windows x64 (win-x64) | Builds; ran under x64 emulation on Windows 11 ARM64 only, not qualified |
Linux glibc x64 (linux-x64) | Builds; not run, not qualified |
| Linux glibc ARM64, macOS x64, macOS ARM64 | Not qualified |
The operating system must still meet the .NET 10 runtime requirements. On
Linux these include glibc and ICU (libicu), which the folder does not
contain; this is not verified yet.
Both hosts generate F# with their built-in backend by default. A separate F#
backend extension, Morphir.CodeGeneration.FSharp.Extension (command
morphir-fsharp-backend), is for MEP hosts that have no built-in F# backend.
morphir generate runs it only when you select it, with --extension or
[codegen.fsharp].extension. See
F# generation.
Standalone morphir install scripts
These scripts install the standalone morphir executable, not morphirup:
# Linux
curl -fsSL https://raw.githubusercontent.com/finos/morphir-dotnet/main/scripts/install-linux.sh | bash
# macOS
curl -fsSL https://raw.githubusercontent.com/finos/morphir-dotnet/main/scripts/install-macos.sh | bash
# Windows
irm https://raw.githubusercontent.com/finos/morphir-dotnet/main/scripts/install-windows.ps1 | iex
See scripts/README.md for their options.
Local Project Installation
Add the Morphir libraries to your project:
dotnet add package Morphir.Core
Build from Source
git clone https://github.com/finos/morphir-dotnet.git
cd morphir-dotnet
dotnet build
Verify the morphir CLI
Check that the dotnet tool is installed correctly:
Then follow Compile F# to Morphir IR.
Troubleshooting
See Troubleshooting.
Release archives (not published yet)
You cannot download these yet: releases do not publish them, and no release or platform is qualified for them.
This section describes what the build packs, so that the troubleshooting below has something to refer to. Use the
installation methods above.
The build can pack four offerings of the same release. Each archive holds its files at the root; extracted anywhere,
including a path with spaces, it runs as morphir (morphir.exe on Windows).
| Offering | Artifact | Needs on the machine | F# |
|---|
native-aot | morphir-dotnet-<release>-native-aot-<rid>.zip or .tar.gz | Nothing from .NET | Through the F# frontend extension (--extension) |
self-contained | morphir-dotnet-<release>-self-contained-<rid>.zip or .tar.gz | Nothing from .NET | Built in |
framework-dependent | morphir-dotnet-<release>-framework-dependent-<rid>.zip or .tar.gz | The .NET 10 runtime (Microsoft.NETCore.App 10.0) for the same architecture | Built in |
dotnet-tool | Morphir.Tool.<release>.nupkg, as this build packs it | The .NET SDK 10.0 to install it, and the .NET 10 runtime to run it | Built in |
The dotnet-tool row is the package that this build packs from the current source. It is not the Morphir.Tool
package already on NuGet that the global tool installation installs.
The operating-system prerequisites of these archives are not established: the minimum glibc version on Linux,
whether ICU and OpenSSL libraries must be installed, the minimum macOS version, and what Gatekeeper does with a
downloaded, unsigned executable. Only “needs nothing from .NET” and “needs the .NET 10 runtime” have been checked, on
one win-arm64 build machine.
When the framework-dependent morphir cannot find .NET
The framework-dependent morphir, like dotnet-morphir, cannot start without a .NET runtime, so it cannot explain
the problem itself. The .NET host prints the message instead. There are two cases.
No .NET found. The host prints “You must install .NET to run this application.” This Windows example comes from
the .NET 10.0.14 host on Windows ARM64, where it exited with 0x80008083 (-2147450749):
You must install .NET to run this application.
App: <folder>\morphir.exe
Architecture: arm64
App host version: 10.0.14
.NET location: Not found
The following locations were searched:
Application directory:
<folder>\
Environment variable:
DOTNET_ROOT_ARM64 = <not set>
DOTNET_ROOT = <not set>
Registered location:
HKLM\SOFTWARE\dotnet\Setup\InstalledVersions\arm64\InstallLocation = <not set>
Default location:
C:\Program Files\dotnet
Learn more:
https://aka.ms/dotnet/app-launch-failed
Download the .NET runtime:
https://aka.ms/dotnet-core-applaunch?missing_runtime=true&arch=arm64&rid=win-arm64&os=win10&apphost_version=10.0.14
Another .NET version found. More common is a machine with .NET, but not a version this morphir can run on. The
host then prints “You must install or update .NET to run this application.”, the framework and version it needs, and
the frameworks it found. This Windows example was made by asking for a version the machine does not have; it exited
with 0x80008096 (-2147450730):
You must install or update .NET to run this application.
App: <folder>\morphir.exe
Architecture: arm64
Framework: 'Microsoft.NETCore.App', version '11.0.0' (arm64)
.NET location: <dotnet folder>
The following frameworks were found:
10.0.13 at [<dotnet folder>\shared\Microsoft.NETCore.App]
10.0.14 at [<dotnet folder>\shared\Microsoft.NETCore.App]
Learn more:
https://aka.ms/dotnet/app-launch-failed
To install missing framework, download:
https://aka.ms/dotnet-core-applaunch?framework=Microsoft.NETCore.App&framework_version=11.0.0&arch=arm64&rid=win-arm64&os=win10
The released framework-dependent morphir asks for Microsoft.NETCore.App 10.0.0 with the default roll-forward:
any installed 10.0 patch serves (the newest one is used), and so does a later 10.x minor when no 10.0 patch is
installed. .NET 9 or .NET 11 alone does not.
On Linux and macOS the messages name Unix paths, and the “Registered location” line names
/etc/dotnet/install_location instead of the registry. A shell there sees only the low 8 bits of the exit code:
131 for “no .NET” and 150 for “another version”. These Linux and macOS details follow from the host’s documented
behavior and are not verified here.
The host looks for .NET in this order: the application folder (where a self-contained application carries it), the
DOTNET_ROOT_<ARCH> and DOTNET_ROOT variables, the registered location, and the default location.
| Operating system | Registered location | Default location |
|---|
| Windows | HKLM\SOFTWARE\dotnet\Setup\InstalledVersions\<arch>\InstallLocation | C:\Program Files\dotnet |
| Linux | /etc/dotnet/install_location, or /etc/dotnet/install_location_<arch> | /usr/share/dotnet, or /usr/lib/dotnet for some distribution packages |
| macOS | /etc/dotnet/install_location, or /etc/dotnet/install_location_<arch> | /usr/local/share/dotnet |
An x64 morphir running under emulation on an ARM64 machine (Windows or macOS) looks for the x64 .NET in the x64
subfolder of the default location, for example C:\Program Files\dotnet\x64 or /usr/local/share/dotnet/x64, and
reads DOTNET_ROOT_X64. Only the Windows row was observed here, in the host’s own output on Windows ARM64; the Linux and macOS rows and the emulation subfolder follow the host’s documented behavior and are not verified here.
To fix it, do one of these:
- Install the .NET 10 runtime for the architecture that the message names, from the download link it prints.
- If .NET is installed in a folder of your own, set
DOTNET_ROOT (or DOTNET_ROOT_ARM64, DOTNET_ROOT_X64) to
that folder. - Use the
self-contained or native-aot archive instead; neither needs an installed .NET.
3 - Compile F# to Morphir IR
Write a business rule in F#, compile it to Morphir IR, and generate code from it in five minutes
Acme Car Rental keeps its daily rates in F#. In five minutes you turn that rule into
Morphir IR, and then into C# that another team can call.
graph LR
A[Rates.fs] -->|dotnet-morphir compile| B[morphir-ir.json]
B -->|dotnet-morphir generate| C[C# or F# code]You need the .NET SDK 10.0 or later.
dotnet tool install --global Morphir.Tool --prerelease
dotnet-morphir --version
2. Write the rule
mkdir -p acme/src && cd acme
Save this as src/Rates.fs:
namespace Acme.CarRental
module Rates =
type VehicleClass =
| Economy
| Compact
| Suv
let dailyRate (vehicle: VehicleClass) : int =
match vehicle with
| Economy -> 4000
| Compact -> 5000
| Suv -> 8000
3. Compile it
dotnet-morphir compile --language fsharp --input src/Rates.fs --package-name acme/car-rental
compile succeeded: 1 module(s), IR version 4, /work/acme/.morphir/out/compile.dest/morphir-ir.json; no diagnostics
/work/acme/.morphir/out/compile.dest/morphir-ir.json
The IR is in .morphir/out/compile.dest/morphir-ir.json. Every later command can find it there.
4. Generate code
dotnet-morphir generate --target csharp --output generated/csharp
p_Acme.CarRental/m_RatesModule.g.cs
generate reads the IR of the most recent compile, so it needs no --input. Use
--target fsharp for F#.
Do both steps in one run:
dotnet-morphir compile --language fsharp --input src/Rates.fs --package-name acme/car-rental \
--target csharp --generate-output generated/csharp
Compile without .NET installed
To compile F# on a machine with no .NET, use morphirup: it installs the Morphir CLI with its own .NET runtime and the
F# frontend. Follow Your first model with morphirup. To build
the self-contained F# frontend from a checkout instead, see
Build the self-contained F# frontend.
Next steps
4 - Validating IR Files
Check a Morphir IR file with morphir validate
morphir validate reads a Morphir IR file and checks it. Use it on IR that compile wrote, or on IR from another
tool. The commands below run the Morphir CLI through morphirup, as in
Your first model with morphirup. With the .NET tool, write
dotnet-morphir in place of morphirup run --.
Check a file
In the walkthrough’s project, after morphirup run -- compile:
morphirup run -- validate --input .morphir/out/compile.dest/morphir-ir.json
0 errors, 0 warnings, 0 info, 0 hints. Analyzers: 1 ran, 0 not run, 0 failed.
The exit code is 0 when there are no errors, so you can use the command in a script or a CI job. The checks find
type mismatches, such as a string where the model declares an int.
For a report that a script can read, add --json:
morphirup run -- validate --input .morphir/out/compile.dest/morphir-ir.json --json
{
"host": "morphir",
"success": true,
"exitCode": 0,
...
"analyzers": [
{
"id": "morphir.type-validator",
"status": "ran"
}
],
"diagnostics": []
}
When the file can’t be read
If the file is not IR that Morphir can read, validate stops with exit code 2 and says where the problem is. Here
formatVersion is the string "3" instead of the number 3:
Error: unsupported-version: ...\bad-ir.json at $.formatVersion: Expected a classic IR version: integer 1, 2 or 3, or release 3.0.x or 3.1.x.
Troubleshooting lists the usual causes.
Check against the JSON schema
morphir ir verify <file> checks a file against the JSON schema of IR formats 1, 2 and 3. Today it also reports
errors for IR that compile writes, in format 4 and in format 3, so use validate for those files. See the
ir verify reference.
Next steps
5 - Generate F# from Morphir
Build and run a car rental model using the Myriad plugin
Compile a Morphir model JSON file into F# as part of an ordinary build. This article uses the supplied car rental example and calls its generated inventory and billing functions.
Prerequisites
- A checkout of
finos/morphir-dotnet. - The .NET SDK pinned in
global.json, currently 10.0.403. - Access to NuGet for package restore.
Run the following commands from the repository root. The checked-in model is sufficient; a separate Morphir frontend is not required.
Choose the plugin version
Morphir.Myriad is on NuGet.org, with a version for each morphir-dotnet release. This article uses 0.4.0-alpha.2; use the version of the release you want. The package brings everything the build needs; the plugin reference lists the versions it works with.
The example’s examples/car-rental/CarRental.fsproj references the plugin and declares the JSON input once:
<ItemGroup>
<PackageReference Include="Morphir.Myriad" Version="0.4.0-alpha.2" PrivateAssets="all" />
<MorphirModel Include="models/car-rental-v3.json" />
<Compile Include="Program.fs" />
</ItemGroup>
In your own project, write the version as above. In this repository the example takes it from the property MorphirMyriadVersion, which is why the commands below pass -p:MorphirMyriadVersion. Don’t add the JSON file to Compile, and don’t list the generated modules by hand.
Restore and run
dotnet restore examples/car-rental/CarRental.fsproj \
--configfile examples/car-rental/NuGet.config -p:MorphirMyriadVersion=0.4.0-alpha.2
dotnet run --project examples/car-rental/CarRental.fsproj --no-restore -p:MorphirMyriadVersion=0.4.0-alpha.2
The supplied NuGet configuration restores every package from NuGet.org, and keeps the repository’s own source mapping out of the example. The application prints:
dailyRate=5000
inventory=Available
reservation=Reserved
pickup=Rented
return=Available
invoice=15000
rejections=7
The first generated value is Car.Rental.Inventory.dailyRate. The driver also calls Inventory.lookup "CAR-1" [ Inventory.car ], then executes the reservation, pickup, return and billing functions. Three billable days at 5000 cents per day produce a 15000-cent invoice. The final line confirms that seven invalid operations returned the expected business errors.
Inspect the F# output
Look beneath examples/car-rental/obj/<configuration>/net10.0/morphir/. The model has seven generated files: Domain.fs, Inventory.fs, Reservation.fs, Pickup.fs, Return.fs, Billing.fs and Workflow.fs. The accompanying manifest.json lists them in dependency order. Myriad inserts them before Program.fs in compilation.
Edit the model JSON or its optional F# authoring script, then rebuild. Generated files are build artifacts and are regenerated; do not edit them directly.
Next steps
Follow the car rental tutorial for the full model, state transitions, error cases and a rate-change exercise. Consult the plugin reference for configuration, compatibility and build failures.
6 - Configure a workspace
Workspace discovery, inspection, safe TOML editing and .NET consumption
morphir.toml declares a workspace, its member projects and their settings. This page shows how to declare a workspace, inspect the settings that apply, add a personal override, and move a morphir-elm project to morphir.toml.
Declare the workspace
Create morphir.toml at the repository root:
[workspace]
members = ["packages/*"]
exclude = ["packages/experimental-*"]
default_member = "packages/orders"
out_dir = ".morphir/out"
[ir]
format_version = 4
[ui]
theme = "light"
Create packages/orders/morphir.toml for the member project:
[project]
name = "Acme.Orders"
source_directory = "src"
exposed_modules = ["Orders.Api"]
Create its src folder. A command run in packages/orders/src finds both files: the member’s own, and the workspace’s at the root. The root has no [project] section, so it is only a workspace, not another project.
To discover a workspace from your own .NET code, see Discover a workspace from a host library.
Inspect the effective values
From the repository root, these commands inspect settings and discovery:
morphir config get ir.format_version --show-origin
morphir config path
morphir config explain ui.theme
The format version comes from the root TOML file. path reports consulted sources and their status; explain identifies every contributing layer for the theme and the winner.
For a member-only value, select its canonical workspace path:
morphir config get project.name --project packages/orders --json
morphir config get ui.theme --workspace --show-origin --show-scope
morphir config list --isolated --json
A scoped query does not fill absent settings from other scopes. --file path
inspects only that file; --config path adds a selected-file layer during effective
resolution. Empty strings and arrays succeed; a missing key exits 1. Usage or
load failures exit 2. See the query API
and Microsoft host example to use
these results without the CLI. Existing IR commands do not adopt all settings
merely because inspection can show them.
Add and remove a personal override
Preview and apply a personal override from the same directory:
morphir config set ui.theme dark --user --dry-run --json
morphir config set ui.theme dark --user
morphir config get ui.theme --show-origin
morphir config unset ui.theme --user
morphir config get ui.theme --show-origin
The workspace declares packages/orders as its default member, so --user targets packages/orders/morphir.user.toml. Without a selected member, a virtual workspace root uses root-adjacent morphir.user.toml. Without a higher override, the first query returns dark from that file. After unset, it returns inherited light from the workspace primary. Removal reveals the inherited value rather than deleting the setting from every source.
Add morphir.user.*, applicable member override paths, and .morphir/out/ to your ignore policy. The configuration system does not change ignore files automatically.
Preview reports the target, redacted before/after values and the effective winner without writing. If an environment variable or profile still wins, a successful file edit still exits 0. For example:
MORPHIR_UI__THEME=light morphir config set ui.theme dark --user --json
morphir config set ui.color false --type bool --workspace --dry-run
Running the same set again reports unchanged and doesn’t rewrite the file. unset of a missing value exits 1. An invalid value, a file you can’t write, or a file that changed while you edited it exits 2: look at the file and run the command again. The config reference has the details.
Retain an existing JSON project
A member may retain its morphir-elm morphir.json:
{
"name": "Acme.Orders",
"sourceDirectory": "elm-src",
"exposedModules": ["Orders.Api"]
}
If a TOML primary also sets project.source_directory = "src", that value wins while the JSON-only fields survive. A scoped edit creates or updates TOML and leaves JSON unchanged. Removing that TOML key reveals elm-src again. A legacy field and its canonical spelling in the same JSON file are an error.
Migrate the classic project
The command is available through the CLI and shared migration APIs.
For this example, start with a classic project whose only primary is the JSON file above. If the earlier TOML example is also present, its different source directory is a conflict that migration must report before writing.
Preview the conversion, then apply it to that member:
morphir project migrate packages/orders/morphir.json --dry-run --json > migration-plan.json
morphir project migrate packages/orders/morphir.json
morphir project migrate packages/orders/morphir.json --json # unchanged repeat
The resulting packages/orders/morphir.toml includes the mapped fields and the defaults that previously came from recognizing a classic Elm manifest:
[project]
name = "Acme.Orders"
source_directory = "elm-src"
exposed_modules = ["Orders.Api"]
[frontend]
language = "elm"
[pipeline]
backend = "checkpoint"
checkpoint_format = "morphir-json"
Migration checks that effective project values and source-path meaning are unchanged when the classic source is excluded. The comparison excludes machine, personal, environment, CLI, and activated-profile overlays while retaining declared base workspace context. It does not pin a new IR version or copy those overrides into the project file. The result reports verification and any fields requiring manual decisions.
The source JSON stays unchanged so older morphir-elm consumers can continue using it. Repeating migration with the same inputs makes no changes. A conflicting TOML value, unsupported dependency/decorations shape, or any unmapped setting blocks application rather than silently dropping data. Remove the old manifest separately only after those consumers have moved to the new format.
See the migration command reference for output selection and structured dry-run results.
Adopt a repository of classic projects
If the repository has several classic projects and no new workspace yet, preview the bounded workspace migration from its root:
morphir workspace migrate . --include 'packages/*' --exclude 'packages/experimental-*' --dry-run
morphir workspace migrate . --include 'packages/*' --exclude 'packages/experimental-*'
The plan lists projects, exclusions, nested workspace boundaries, already migrated members, TOML edits, proposed membership, and intended member/default-selection and shared-output-root changes. It checks each classic-to-TOML conversion in the same proposed workspace context and verifies the resulting workspace before applying the batch. One blocked selected project blocks the whole plan. Existing membership/settings and every classic manifest are preserved; an equivalent repeat makes no changes.
Adopting a shared workspace can change standalone output defaults to <workspace>/.morphir/out. The preview reports that change separately from serialization equivalence; it does not move previously generated files.
For two eligible projects under packages/orders and packages/payments, a new
virtual workspace contains explicit member paths:
[workspace]
members = ["packages/orders", "packages/payments"]
Each converted member receives its own TOML primary with the same loss checks as
single-project migration. If the repository root itself has a project, migration
keeps it as the root project rather than adding "." to the member list. Existing
membership patterns, exclusions, valid default selection, comments, and settings
remain in place. Preview the operation from the directory you intend to adopt;
invoking from a child deliberately limits the operation to that child.
Use JSON in automation and retain the result when application fails:
morphir workspace migrate . --dry-run --json > workspace-preview.json
morphir workspace migrate . --json > workspace-result.json
morphir workspace migrate . --json # unchanged repeat
Exit code 2 means the migration was blocked or failed. If the result says recovery is required, don’t run it again
yet: follow the recovery guide.
Read the workspace migration reference for batch recovery and supported controls.
Inspect configuration in CI
Create morphir.profile.ci.toml beside the root primary:
The shared loader accepts explicitly selected profiles. This command selects the profile and captures JSON output:
morphir config show --isolated --discovery-boundary . --profiles ci --json > effective-config.json
This excludes machine configuration and personal overrides while retaining the selected profile. Environment values still participate. Stdout contains JSON; diagnostics go to stderr. Merely setting CI=true does not activate a named profile through the shared loader. Legacy cache callers retain their separate CiProfileMode behavior.
Read the configuration guide for all layouts and precedence rules, the command reference for options, and troubleshooting for unexpected results.
7 - Python WASM extension (experimental)
Compile and generate Python with the pinned Python extension in the .NET CLI
This experimental feature runs the Python 0.6.0 extension inside the Morphir CLI. It compiles a small Python
file to Morphir IR and generates Python from it. You don’t need Python installed.
Check that your CLI can run WASM extensions with morphir extension runtimes --json. It can on Windows ARM64, macOS
and Linux (glibc). Windows x64 can’t run WASM extensions yet
(#609); the other commands still work there.
From a new project directory, download and verify the published module with
PowerShell:
New-Item -ItemType Directory -Force .morphir/extensions | Out-Null
Invoke-WebRequest 'https://github.com/finos/morphir-python/releases/download/extension/python/v0.6.0/morphir-python-binding-0.6.0.wasm' -OutFile .morphir/extensions/python.wasm
if ((Get-FileHash .morphir/extensions/python.wasm -Algorithm SHA256).Hash.ToLowerInvariant() -ne 'ce0cba07f0395ff8179b42eb021ade05c68197f69b8e9c0c8bc369d2a035727c') { throw 'Python module digest mismatch' }
Create morphir.toml:
[project]
name = "acme/demo"
[extensions.python]
path = ".morphir/extensions/python.wasm"
abi = "extism-kernel-v1"
sha256 = "ce0cba07f0395ff8179b42eb021ade05c68197f69b8e9c0c8bc369d2a035727c"
Create identity.py:
def identity(value: int) -> int:
return value
Compile it, then generate Python from the successful compile record:
morphir compile --project . --language python --input identity.py --ir-version 4 --json
morphir generate --project . --target python --output generated --json
To generate from an existing IR file instead:
morphir generate --project . --input morphir-ir.json --target python --output generated --json
The [extensions.python] table makes --language python and --target python use the module. The CLI checks the
module’s sha256 before it runs it. JSON results go to stdout and the extension’s messages to stderr. If the
extension fails, generate writes no files. The WASM extension reference
explains how the CLI picks an extension and the limits it applies.
The extension supports a small part of Python. Code it doesn’t support, including addition, gives a source
diagnostic and exit code 1, and no IR.
8 - Troubleshooting
Fixes for the problems you are most likely to meet when you install morphirup, set up a toolchain, compile and generate
Each problem below gives what you see, why it happens, and what to do. For problems with a single command, see also
the CLI troubleshooting reference.
Installing morphirup
morphirup is not found
What you see. After installing, the terminal says morphirup is not recognized, or command not found.
Why. The folder that holds morphirup is not on your PATH, or the terminal was open before the install.
What to do. On Windows, the install script adds its folder to your user PATH, so open a new terminal; one that
was already open doesn’t see the change. On macOS and Linux, the script doesn’t change PATH. When its folder isn’t
on your PATH, it ends by printing the line to add:
morphirup-install: .../bin is not on your PATH. Add this line to your shell profile
morphirup-install: (~/.profile, ~/.bashrc or ~/.zshrc), then open a new shell:
export PATH=".../bin:$PATH"
The script prints the full folder where this shows .... Add its export line to your shell profile and open a
new shell. Installation says where each script
installs morphirup.
The install script reports a checksum mismatch
What you see.
SHA-256 mismatch for morphirup-win-arm64.exe: expected 0000000000000000000000000000000000000000000000000000000000000000, got 6bb59736f765a9f2cbdf7262a1f07838c15c6025927f10f7642f4307a8d237f3; nothing was installed
Why. The file the script downloaded is not the one the release published. Usually the download was cut short
or changed on the way.
What to do. Nothing was installed, so it is safe to run the script again. If the mismatch comes back, download
morphirup and the release’s checksum file yourself and compare them, as
Download it yourself shows. If they still don’t match, don’t run the
file, and open an issue.
The install script refuses on an Intel Mac
What you see. On an Intel Mac, the script stops with an error that says Intel macOS is not supported. (A terminal
that runs under Rosetta on an Apple silicon Mac is fine: the script installs the Apple silicon file.)
Why. morphirup is built for Macs with Apple silicon (M1 and later) only.
What to do. Use a Mac with Apple silicon, or a Windows machine.
mise installs an older release, or the wrong one
What you see. Without a version, mise can pick a release that isn’t the newest, or one that has no morphirup
for your machine. With the command from Installation,
mise picked an older release that has no Windows ARM64 file:
mise ✗ github:finos/morphir-dotnet@0.4.0-alpha.4 943ms · failed: No matching asset found for platform windows-arm64
Without version_prefix=v,prerelease=true, it picks the plugin release plugin-v0.1.1, which has no morphirup:
mise ✗ github:finos/morphir-dotnet@plugin-v0.1.1 36ms · failed: No matching asset found for platform windows-arm64
Why. mise doesn’t always see the newest release: its list can leave out one published recently. Without those
options, it takes the release that GitHub marks as latest, which in this repository is a plugin release.
What to do. Give mise the version. Add @<version> at the end, inside the quotes, as
With mise shows. The versions are on the
releases page, written without the v.
What you see.
"status": "refused",
...
"code": "setup.platform_mismatch",
"message": "Release setup requires an actual Windows x64 or ARM64 manager process."
The install script also warns, when it installs morphirup on Linux, that setup will refuse.
Why. setup doesn’t support Linux yet. morphirup itself installs and runs there, but there is no toolchain
for it to set up.
What to do. Use Windows or a Mac with Apple silicon for now.
run or show says the Home needs setup
What you see. One of these, from morphirup run or morphirup show:
morphirup: local.setup_required: This Home was set up with local-toolchains 0.1.0-preview.3, and this morphirup reads only 0.1.0-preview.4. Run morphirup setup --yes again in this Home. ...
morphirup: local.platform_mismatch: Historical local execution requires a win-x64 manager process.
"code": "local.io_refused",
"message": "Local input or installed state could not be safely opened."
Why. morphirup keeps the toolchain in a folder called the Home: .morphir in your user folder, or the folder
that MORPHIR_HOME names. The first message means the Home was set up by an older morphirup. The other two mean
nothing has been set up in this Home yet. On Windows ARM64, run reports this case as a win-x64 problem, which is
misleading. The fix is still to set up.
What to do. Run setup, then try again:
If you set MORPHIR_HOME, check that it names the same folder for setup and run.
Setup fails without a network
What you see. setup (or setup --dry-run) refuses with:
"code": "acquisition.refused",
"message": "The bounded release transfer was refused by transport or byte-integrity checks."
Why. Setup downloads the toolchain from GitHub, so it needs the network. A proxy that is set but doesn’t work
causes the same error.
What to do. Run setup once while you are online, and check HTTPS_PROXY and HTTP_PROXY. Behind a company
proxy or firewall, see Behind a proxy or firewall. After that,
morphirup run -- compile and morphirup run -- generate need no network.
Setup says a path is too long (Windows)
Why. Windows limits a program’s path to 259 characters, and the Home’s folders add to the length.
What to do. Set MORPHIR_HOME to a short folder, such as C:\morphir-home, and run morphirup setup --yes
again.
The release-checksum note
What you see. setup, show and run print this line on stderr:
morphirup: release-checksum delivery, NOT publisher-authenticated.
Why. It tells you how far the download was checked. Every file matched the checksums published with the
release, so you have the files the release published, intact. The files are not signed, so the check doesn’t prove
who published them.
What to do. Nothing. It is a notice, not an error. The
morphirup reference describes the check in detail.
Validating IR
ir verify rejects the IR that compile wrote
What you see. morphir ir verify reports errors for a file that compile wrote. For format 4 it says:
Validation Result: ✗ INVALID
...
Schema Version: v3 (auto)
...
Path: /formatVersion
Message: Expected "3"
For a file compiled with --ir-version 3, it reports a very long list of errors.
Why. ir verify checks against the JSON schema of IR formats 1, 2 and 3. It doesn’t know format 4, and its
schema doesn’t match the format 3 that compile writes.
What to do. Use morphir validate --input <file> instead, as
Validating IR files shows.
validate can’t read the file
validate stops with exit code 2 when the file isn’t IR it can read. The message names the problem and where it is:
| Cause | Message |
|---|
| The file is not complete JSON | Error: malformed-json: ...\broken.json: The input is not a complete JSON document. |
formatVersion is a string, "3", not the number 3 | Error: unsupported-version: ...\bad-ir.json at $.formatVersion: Expected a classic IR version: integer 1, 2 or 3, or release 3.0.x or 3.1.x. |
A tag has the wrong case, "public" for "Public" | Error: decode: ...\case-ir.json at $.distribution[3].modules[0]: Invalid access 'public'. |
Fix the file at the place the message names, and run validate again.
The validate reference has the full list of input rules.
9 - Generate and run C#
Generate a typed Morphir library and execute it in a fresh C# consumer
Generate and run C#
Use a repository checkout, the Morphir CLI built from it, and SDK 10.0.403.
Generation writes source files. Compilation is a separate step and requires the
consumer’s compiler and Morphir.SDK.CSharp package.
The fixture exports a daily rate of 5,000 cents. These PowerShell commands create
a fresh consumer and a local runtime feed without publishing a package:
dotnet pack src/Morphir.SDK.CSharp/Morphir.SDK.CSharp.csproj --configuration Release --output artifacts/csharp-feed -p:Version=0.0.0-local
dotnet new console --framework net10.0 --output artifacts/csharp-pricing
dotnet add artifacts/csharp-pricing package Morphir.SDK.CSharp --version 0.0.0-local --source "$PWD/artifacts/csharp-feed"
morphir generate --input tests/Fixtures/MorphirIR/daily-rate-v4.json --target csharp --output artifacts/csharp-pricing/Generated --option language-version=14 --option namespace=Pricing.Generated --json
Replace artifacts/csharp-pricing/Program.cs with:
using Pricing.Generated;
var cents = InventoryModule.DailyRate();
if (cents != 5000) throw new System.Exception("Unexpected daily rate");
System.Console.WriteLine(cents);
Then compile and execute:
dotnet build artifacts/csharp-pricing --configuration Release -p:LangVersion=14
dotnet run --project artifacts/csharp-pricing --configuration Release --no-build
The program prints 5000. Don’t edit the generated files: call their public API from your own code, and generate
again when the model changes.
Without the namespace option, the generated code uses the model’s package name, here Car.Rental.
Keep the generated output in its own folder, apart from the input. Leave the .morphir-generated.json file in it:
the next generate uses it to update only the files it owns and to keep yours. The
generate reference covers the options, including language-version, and what
happens when generation fails.
See C# troubleshooting for invalid options and model
errors, and Generate F# from Morphir for F#.
10 - Generate F# from a Morphir model
Generate F# source from JSON or Ion libraries
Generate F# from a Morphir model
From a repository checkout, generate the shared daily-rate example:
morphir generate --input tests/Fixtures/MorphirIR/daily-rate-v4.json --target fsharp --output artifacts/generated-fsharp --json
The report lists Inventory.fs. The output contains that file and the
.morphir-generated.json ownership manifest. Generate the same input again to
receive an unchanged receipt without rewriting the file.
Classic JSON and Ion use the same command. For an Ion tree, pass its directory:
morphir generate --input tests/Fixtures/MorphirIon/trees/v3-daily-rate --target fsharp --output artifacts/generated-fsharp --json
Use the report’s artifact order when adding files to an F# project. Generation
does not create the project or compile it. Keep handwritten files separate
from owned artifact paths. See the command reference for
the supported profile, options, exit codes and publication guarantees.
morphir generate --target fsharp uses the F# backend built into the CLI. The same generator also comes as a
separate backend, morphir-fsharp-backend, which morphirup installs; see F# generation.
To run these commands without a checkout or .NET, see
Your first model with morphirup.