Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
c770875
docs: plan Python binding migration
DamianReeves Oct 27, 2026
55ced46
docs: ground Python bridge in community examples
DamianReeves Oct 27, 2026
44631cd
feat(rust): move morphir-python-binding from morphir-rust
DamianReeves Oct 27, 2026
40c4d5b
docs(openspec): mark standalone Rust compiler tasks done
DamianReeves Oct 27, 2026
6c177fb
fix(rust): initialize the MEP session in the binding examples
DamianReeves Oct 27, 2026
dd8feaa
feat(python): add the native compiler bridge and typed API
DamianReeves Oct 27, 2026
ca6ae50
feat(cli): add morphir-python compile and generate
DamianReeves Oct 27, 2026
7eb7852
docs: add the Python compiler guide and BDD scenarios
DamianReeves Oct 27, 2026
559c8e3
chore: keep compiler sample imports out of TYPE_CHECKING blocks
DamianReeves Oct 27, 2026
d545883
build(native): qualify native wheels, source archives and clean installs
DamianReeves Oct 27, 2026
82f4cc4
build: add mise tasks for Rust, native and bundle checks
DamianReeves Oct 27, 2026
4770057
feat(ir): read every v4 spelling the pinned schema accepts
DamianReeves Oct 27, 2026
7a7a8de
build(extension): package and qualify the MEP bundle here
DamianReeves Oct 27, 2026
6053c7a
ci: select checks by change and qualify native and extension releases
DamianReeves Oct 27, 2026
a074e89
fix(native): build the CPython module only with the python feature
DamianReeves Oct 27, 2026
5fda2d0
perf: add a reproducible compiler cost benchmark
DamianReeves Oct 27, 2026
0d9c124
perf: state observations in the benchmark report
DamianReeves Oct 27, 2026
2b96642
fix(compiler): accept empty v4 documentation and test codec agreement
DamianReeves Oct 27, 2026
1880774
chore: ignore built extension bundles
DamianReeves Oct 27, 2026
5f70490
build(native): install and exercise the wheel built from the source a…
DamianReeves Oct 27, 2026
40c2490
docs(openspec): mark API, CLI, packaging and benchmark tasks done
DamianReeves Oct 27, 2026
9fd3c50
docs(openspec): record implementation findings in the design
DamianReeves Oct 27, 2026
9256d56
fix: address review findings on output, errors, v4 names and release …
DamianReeves Oct 27, 2026
0f93653
fix(ci): install the native wheel in release-check
DamianReeves Oct 27, 2026
52359a6
docs(openspec): mark the morphir-rust removal branch prepared
DamianReeves Oct 27, 2026
5278e5a
docs(openspec): mark scoped CI and bundle qualification tasks done
DamianReeves Oct 27, 2026
4b3a4b7
fix(release): make native install qualification portable
DamianReeves Oct 27, 2026
ba00e2b
docs(openspec): mark the finos/morphir provider cutover branch prepared
DamianReeves Oct 27, 2026
b3aa2ff
docs(openspec): mark platform and release qualification tasks done
DamianReeves Oct 27, 2026
b96f63c
docs(openspec): mark the coordinated qualification record done
DamianReeves Oct 27, 2026
f2bfd39
fix: address Codex review findings
DamianReeves Oct 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .config/mise/config.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
[tools]
python = "3.14"
uv = "latest"
# The Rust half under rust/ (compiler, CPython bridge and WASM extension guest).
rust = { version = "1.98.1", targets = "wasm32-unknown-unknown", components = "rustfmt,clippy" }
# Validates the release WASM guest before bundling.
wasm-tools = "1.259.0"

[env]
VIRTUAL_ENV = "{{config_root}}/.venv"
Expand Down
9 changes: 9 additions & 0 deletions .config/mise/tasks/bench
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/usr/bin/env bash
#MISE description="Measure native, bridge, API and CLI compile and generate costs"
# Arguments pass to scripts/bench.py, for example: mise run bench -- --modules 40
set -euo pipefail

# Measure the release wheel, not a development build.
NATIVE_SDIST=0 mise run native:build
uv pip install --quiet --no-deps --reinstall dist/native/morphir_python_native-*.whl
uv run --no-sync python scripts/bench.py "$@"
1 change: 1 addition & 0 deletions .config/mise/tasks/coverage
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
set -euo pipefail

uv run pytest tests/ --cov=packages/morphir/src --cov=packages/morphir-tools/src --cov-report=term-missing --cov-report=html --cov-report=xml
uv run python -m mypy.stubtest morphir_python_native._native
19 changes: 19 additions & 0 deletions .config/mise/tasks/extension/bundle
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/usr/bin/env bash
#MISE description="Build, package and qualify the morphir-python WASM extension bundle offline"
set -euo pipefail

bundle="${BUNDLE_DIR:-$PWD/.morphir/build/extensions/python}"
receipts="${QUALIFICATION_DIR:-$PWD/.dev/qualification}"
mkdir -p "$receipts" "$(dirname "$bundle")"
rm -rf "$bundle"
(cd rust && cargo build --locked --release -p morphir-python-binding --target wasm32-unknown-unknown)
wasm="rust/target/wasm32-unknown-unknown/release/morphir_python_binding.wasm"
wasm-tools validate "$wasm"
commit=()
if [[ -z "$(git status --porcelain --untracked-files=normal)" ]]; then
commit=(--git-commit "$(git rev-parse HEAD)")
fi
uv run --no-sync python scripts/extension_bundle.py package --wasm "$wasm" --output "$bundle" "${commit[@]}" \
>"$receipts/bundle.json"
uv run --no-sync python scripts/extension_bundle.py host-check --bundle "$bundle" >"$receipts/bundle-host.json"
cat "$receipts/bundle-host.json"
14 changes: 13 additions & 1 deletion .config/mise/tasks/install
Original file line number Diff line number Diff line change
@@ -1,8 +1,20 @@
#!/usr/bin/env bash
#MISE description="Install all dependencies and workspace packages"
#
# By default uv builds morphir-python-native from source, which needs Rust.
# MORPHIR_NATIVE_WHEEL=PATH installs that prebuilt wheel instead, so a CI job can
# reuse one wheel. MORPHIR_SKIP_NATIVE=1 omits the native compiler for jobs that
# only lint or type-check.
set -euo pipefail

echo "Syncing dependencies..."
uv sync --frozen --all-packages --all-groups
if [[ -n "${MORPHIR_NATIVE_WHEEL:-}" || "${MORPHIR_SKIP_NATIVE:-0}" == 1 ]]; then
uv sync --frozen --all-packages --all-groups --no-install-package morphir-python-native
if [[ -n "${MORPHIR_NATIVE_WHEEL:-}" ]]; then
uv pip install --no-deps "$MORPHIR_NATIVE_WHEEL"
fi
else
uv sync --frozen --all-packages --all-groups
fi

echo "Install complete!"
16 changes: 16 additions & 0 deletions .config/mise/tasks/native/build
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/usr/bin/env bash
#MISE description="Build the morphir-python-native wheel and source archive into dist/native"
set -euo pipefail

out="${NATIVE_DIST:-$PWD/dist/native}"
mkdir -p "$out"
cd packages/morphir-python-native
# Portable wheels: refuse host-specific CPU settings.
if [[ "${RUSTFLAGS:-}" == *target-cpu* || "${CARGO_ENCODED_RUSTFLAGS:-}" == *target-cpu* ]]; then
echo "RUSTFLAGS must not select a target CPU for portable wheels" >&2
exit 1
fi
uvx --from maturin==1.15.0 maturin build --release --locked --out "$out" "$@"
if [[ "${NATIVE_SDIST:-1}" == 1 ]]; then
uvx --from maturin==1.15.0 maturin sdist --out "$out"
fi
44 changes: 44 additions & 0 deletions .config/mise/tasks/native/qualify
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#!/usr/bin/env bash
#MISE description="Qualify built native, core and tools distributions without Rust on PATH"
set -euo pipefail

native="${NATIVE_DIST:-$PWD/dist/native}"
receipts="${QUALIFICATION_DIR:-$PWD/.dev/qualification}"
mkdir -p "$receipts"
shopt -s nullglob
if [[ ${RELEASE_TAG:-} == python-v* ]]; then
# A CLI release ships with the published native compiler, so qualify those bytes,
# and refuse if the compiler sources changed after that native release.
pin="$(python3 -c 'import sys; sys.path.insert(0, "scripts"); import qualify_native as q; print(q.tools_pin())')"
git fetch --quiet --depth=1 origin "refs/tags/native-v$pin:refs/tags/native-v$pin"
if ! git diff --quiet "native-v$pin" HEAD -- rust packages/morphir-python-native; then
echo "the native compiler changed after native-v$pin; release a new native version first" >&2
exit 1
fi
native="$receipts/published-native"
rm -rf "$native"
uvx pip download --quiet --no-deps --only-binary=:all: --implementation cp \
--python-version 3.14 --abi abi3 --platform manylinux_2_28_x86_64 \
--dest "$native" "morphir-python-native==$pin"
export SKIP_SDIST_BUILD=1
fi
wheels=("$native"/morphir_python_native-*.whl)
if [[ ${#wheels[@]} -ne 1 ]]; then
echo "expected exactly one native wheel in $native" >&2
exit 1
fi
python3 scripts/qualify_native.py wheel "${wheels[0]}" >"$receipts/native-wheel.json"
sdists=("$native"/morphir_python_native-*.tar.gz)
if [[ ${#sdists[@]} -eq 1 && "${SKIP_SDIST_BUILD:-0}" != 1 ]]; then
rm -rf "$receipts/from-sdist"
python3 scripts/qualify_native.py sdist "${sdists[0]}" --out "$receipts/from-sdist" \
>"$receipts/native-sdist.json"
# The wheel built from the archive alone must work when installed, too.
python3 scripts/qualify_native.py install \
packages/morphir/dist/morphir-*.whl "$receipts"/from-sdist/morphir_python_native-*.whl \
packages/morphir-tools/dist/morphir_python-*.whl >"$receipts/sdist-install.json"
fi
python3 scripts/qualify_native.py install \
packages/morphir/dist/morphir-*.whl "${wheels[0]}" packages/morphir-tools/dist/morphir_python-*.whl \
>"$receipts/clean-install.json"
cat "$receipts"/*.json
20 changes: 17 additions & 3 deletions .config/mise/tasks/release-check
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,25 @@ set -euo pipefail
release_dir="$(pwd)/.dev/release-check"
mkdir -p "$release_dir"
uv lock --check
python scripts/check_distributions.py
python -m scripts.check_distributions
uvx --from twine==7.0.0 twine check --strict \
packages/morphir/dist/* packages/morphir-tools/dist/*
uv export --frozen --all-packages --no-default-groups --no-emit-workspace \
--no-hashes --output-file "$release_dir/constraints.txt"

# The CLI needs its exact native compiler. Use the wheel CI built, unless this is
# a CLI release, which resolves the already published native release from PyPI.
native_wheels=()
if [[ ${RELEASE_TAG:-} != python-v* ]]; then
shopt -s nullglob
native_wheels=("${NATIVE_DIST:-$PWD/dist/native}"/morphir_python_native-*.whl)
shopt -u nullglob
if [[ ${#native_wheels[@]} -ne 1 ]]; then
echo "expected one native wheel in ${NATIVE_DIST:-dist/native}; run mise run native:build" >&2
exit 1
fi
fi

packages=(morphir morphir-python)
if [[ ${RELEASE_TAG:-} == morphir-v* ]]; then
packages=(morphir)
Expand Down Expand Up @@ -42,7 +55,7 @@ assert find_spec("typer") is None
PY_CORE
else
uv pip install --python "$release_python" --constraint "$release_dir/constraints.txt" \
packages/morphir/dist/*.whl packages/morphir-tools/dist/*.whl
packages/morphir/dist/*.whl packages/morphir-tools/dist/*.whl "${native_wheels[@]}"
"$release_python" -I - <<'PY_CLI'
from importlib.metadata import distribution, version

Expand All @@ -60,6 +73,7 @@ PY_CLI
fi
uv pip check --python "$release_python"
uv pip freeze --python "$release_python" --exclude morphir --exclude morphir-python \
--exclude morphir-python-native \
> "$release_dir/$package-requirements.txt"
if [[ -s "$release_dir/$package-requirements.txt" ]]; then
uvx --from pip-audit==2.10.1 pip-audit --strict --no-deps --disable-pip \
Expand All @@ -86,7 +100,7 @@ uv pip install --python "$migration_python" --constraint "$release_dir/constrain
'morphir-python==0.0.1'
uv pip uninstall --python "$migration_python" morphir-python morphir
uv pip install --python "$migration_python" --constraint "$release_dir/constraints.txt" \
packages/morphir/dist/*.whl packages/morphir-tools/dist/*.whl
packages/morphir/dist/*.whl packages/morphir-tools/dist/*.whl "${native_wheels[@]}"
"$migration_python" -I - <<'PY_MIGRATION'
from importlib.metadata import version

Expand Down
34 changes: 34 additions & 0 deletions .config/mise/tasks/rust/check
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#!/usr/bin/env bash
#MISE description="Format, lint and test the Rust workspace; build the WASM guest without CPython"
set -euo pipefail
cd rust

# mise points VIRTUAL_ENV at .venv, which a Rust-only job does not create.
if [[ -n "${VIRTUAL_ENV:-}" && ! -x "$VIRTUAL_ENV/bin/python" ]]; then
unset VIRTUAL_ENV
fi
export PYO3_PYTHON="${PYO3_PYTHON:-$(command -v python3)}"

cargo fmt --all --check
cargo clippy --locked --all-targets -- -D warnings
# The CPython module code builds only with the feature Maturin enables.
cargo clippy --locked -p morphir-python-native --features python -- -D warnings
cargo test --locked
# The generated-code execution test runs Python; it is ignored by default.
MORPHIR_TEST_PYTHON="${MORPHIR_TEST_PYTHON:-python3}" \
cargo test --locked -p morphir-python-binding --test functions -- --ignored
cargo run --locked --quiet -p morphir-python-binding --example python_adt >/dev/null
cargo run --locked --quiet -p morphir-python-binding --example python_modules -- 3 >/dev/null

# The compiler crate, its native consumers and the WASM guest never link CPython.
if cargo tree --locked -p morphir-python-binding -e normal,build | grep -q pyo3; then
echo "morphir-python-binding must not depend on PyO3" >&2
exit 1
fi
cargo build --locked --release -p morphir-python-binding --target wasm32-unknown-unknown
wasm-tools validate target/wasm32-unknown-unknown/release/morphir_python_binding.wasm
if wasm-tools print target/wasm32-unknown-unknown/release/morphir_python_binding.wasm \
| grep -E '^\s*\(import' | grep -v '"extism:host/env"'; then
echo "the WASM guest imports something other than the Extism host" >&2
exit 1
fi
30 changes: 30 additions & 0 deletions .config/mise/tasks/rust/consumer
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#!/usr/bin/env bash
#MISE description="Build and run an independent Cargo consumer of this commit's compiler crate"
set -euo pipefail

repository="$(git rev-parse --show-toplevel)"
revision="$(git rev-parse HEAD)"
consumer="$(mktemp -d)/consumer"
trap 'rm -rf "$(dirname "$consumer")"' EXIT

cargo new --quiet "$consumer"
cat >>"$consumer/Cargo.toml" <<TOML
morphir-python-binding = { git = "file://$repository", rev = "$revision" }
morphir-extension-sdk = "=0.3.0"
serde_json = "1"
TOML
# The README example is the consumer's main program.
python3 - "$repository/rust/crates/morphir-python-binding/README.md" "$consumer/src/main.rs" <<'PY'
import sys
text = open(sys.argv[1]).read()
start = text.index("## Use the crate from another Rust project")
body = text.index("```rust\n", start) + len("```rust\n")
open(sys.argv[2], "w").write(text[body:text.index("```", body)])
PY
output="$(cd "$consumer" && cargo run --quiet)"
grep -q "def choose(" <<<"$output"
if grep -q 'name = "pyo3' "$consumer/Cargo.lock"; then
echo "the consumer resolved PyO3" >&2
exit 1
fi
echo "independent consumer at $revision compiled and generated Python"
2 changes: 2 additions & 0 deletions .config/mise/tasks/test
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@
set -euo pipefail

uv run pytest tests/
# The native stubs must match the installed extension module.
uv run python -m mypy.stubtest morphir_python_native._native
4 changes: 2 additions & 2 deletions .config/mise/tasks/typecheck
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
set -euo pipefail

echo "Running mypy..."
uv run mypy packages/morphir/src packages/morphir-tools/src scripts
uv run mypy packages/morphir/src packages/morphir-tools/src packages/morphir-python-native/python scripts

echo "Running pyright..."
uv run pyright packages/morphir/src packages/morphir-tools/src
uv run pyright packages/morphir/src packages/morphir-tools/src packages/morphir-python-native/python
Loading
Loading