This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

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.

PlatformSupported
Windows x64 and ARM64Yes
macOS on Apple siliconYes
Linux x64 and ARM64morphirup 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:

morphirup --version
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.

3. Set up the toolchain

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:

morphirup setup --yes
{
  ...
  "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:

morphirup show
{
  ...
  "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 run -- compile
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:

morphirup --version

It prints the version you installed.

Script options

TomacOS and LinuxWindows
Install a given release--version <version>-Version <version>
Install into another folder--install-dir <dir>-InstallDir <dir>
Leave PATH aloneThe 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:

MachineFile
macOS, Apple siliconmorphirup-osx-arm64
Linux x64morphirup-linux-x64
Linux ARM64morphirup-linux-arm64
Windows x64morphirup-win-x64.exe
Windows ARM64morphirup-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.

What works on each platform

PlatformInstall morphirupSet up, compile and generate
macOS on Apple siliconYesYes
Windows x64 and ARM64YesYes
Linux x64 and ARM64YesNot yet: morphirup setup does not support Linux yet
macOS on IntelNoNo

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):

OfferingTo run itTo install itReleased
Native AOT morphirNothing from .NETNothingNot yet as this offering
Self-contained morphir (carries its own .NET runtime)Nothing from .NETNothingNot yet
Framework-dependent morphirThe .NET runtime Microsoft.NETCore.App 11.xNothingNot yet
dotnet-morphir (Morphir.Tool dotnet tool)The .NET runtime Microsoft.NETCore.App 11.xA .NET SDK 11.0.100 or later, for dotnet tool installYes, 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.

Global Tool Installation

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.

PlatformSelf-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 ARM64Not 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:

dotnet-morphir --version

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).

OfferingArtifactNeeds on the machineF#
native-aotmorphir-dotnet-<release>-native-aot-<rid>.zip or .tar.gzNothing from .NETThrough the F# frontend extension (--extension)
self-containedmorphir-dotnet-<release>-self-contained-<rid>.zip or .tar.gzNothing from .NETBuilt in
framework-dependentmorphir-dotnet-<release>-framework-dependent-<rid>.zip or .tar.gzThe .NET 10 runtime (Microsoft.NETCore.App 10.0) for the same architectureBuilt in
dotnet-toolMorphir.Tool.<release>.nupkg, as this build packs itThe .NET SDK 10.0 to install it, and the .NET 10 runtime to run itBuilt 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 systemRegistered locationDefault location
WindowsHKLM\SOFTWARE\dotnet\Setup\InstalledVersions\<arch>\InstallLocationC:\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:

  1. Install the .NET 10 runtime for the architecture that the message names, from the download link it prints.
  2. If .NET is installed in a folder of your own, set DOTNET_ROOT (or DOTNET_ROOT_ARM64, DOTNET_ROOT_X64) to that folder.
  3. 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]

1. Install the tool

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.

Configure the model input

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

Configure a workspace

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:

[ui]
color = false

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.

Setting up a toolchain

Linux: setup says the platform isn’t supported

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:

morphirup setup --yes

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:

CauseMessage
The file is not complete JSONError: malformed-json: ...\broken.json: The input is not a complete JSON document.
formatVersion is a string, "3", not the number 3Error: 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.