Skip to content

Support multi-level (transitive) closure capture #1918

Support multi-level (transitive) closure capture

Support multi-level (transitive) closure capture #1918

Workflow file for this run

name: ci
on:
push:
branches:
- main
tags:
- '**'
pull_request: {}
workflow_dispatch:
inputs:
run_release:
description: 'Run release jobs (for manual tag releases)'
type: boolean
default: false
permissions: {}
env:
COLUMNS: 150
UV_PYTHON: '3.14'
UV_FROZEN: '1'
DEBUG: 'napi:*'
LLVM_COV_IGNORE_FILENAME_REGEX: '(tests/|test_cases/|/tests\.rs$$)'
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
# nightly is only used for `cargo +nightly fmt` (make format-rs); clippy must
# run on stable. Install both toolchains, then explicitly set stable as the
# default so bare `cargo clippy` (make lint-rs) resolves to stable — dtolnay's
# install order alone does not reliably control which toolchain is the default.
# no release versioning, see https://github.com/dtolnay/rust-toolchain/issues/180
- uses: dtolnay/rust-toolchain@efa25f7f19611383d5b0ccf2d1c8914531636bf9
with:
toolchain: nightly
components: rustfmt
# no release versioning, see https://github.com/dtolnay/rust-toolchain/issues/180
- uses: dtolnay/rust-toolchain@efa25f7f19611383d5b0ccf2d1c8914531636bf9
with:
toolchain: stable
components: clippy
- name: Set stable as the default toolchain (clippy on stable; fmt pins +nightly)
run: rustup default stable
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
lookup-only: false # zizmor: ignore[cache-poisoning] -- Job does not produce release artifacts and does not have sensitive permissions
cache-on-failure: true
prefix-key: 'v1-rust'
- uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
with:
enable-cache: true # zizmor: ignore[cache-poisoning] -- Job does not produce release artifacts and does not have sensitive permissions
- run: uv sync --all-packages --only-dev
- name: Setup node
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 # zizmor: ignore[cache-poisoning] -- Job does not produce release artifacts and does not have sensitive permissions
with:
node-version: 24
cache: npm
cache-dependency-path: crates/monty-js/package-lock.json
- name: Install node dependencies
run: npm install
working-directory: crates/monty-js
- name: Run prek
uses: j178/prek-action@cbc2f23eb5539cf20d82d1aabd0d0ecbcc56f4e3 # v2.2
env:
SKIP: no-commit-to-branch
# the protobuf schema and the checked-in prost output must never drift
- name: Check generated protobuf code is in sync
run: make check-proto
test-rust:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
# no release versioning, see https://github.com/dtolnay/rust-toolchain/issues/180
- uses: dtolnay/rust-toolchain@efa25f7f19611383d5b0ccf2d1c8914531636bf9
with:
toolchain: stable
components: llvm-tools
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
lookup-only: false # zizmor: ignore[cache-poisoning] -- Job does not produce release artifacts and does not have sensitive permissions
cache-on-failure: true
- uses: taiki-e/install-action@1ed3272338f573e042a2e6bca3893aa19f43b47a # v2.71.3
with:
tool: cargo-llvm-cov
# this means pyo3 will use Python 3.14 in tests
- name: set up python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: '3.14'
- run: rustc --version --verbose
- run: python3 -V
# don't use .venv python in CI
- run: rm .cargo/config.toml
- run: cargo llvm-cov clean --workspace
# coverage for `make test-no-features`
- run: cargo llvm-cov --no-report -p monty
- run: cargo llvm-cov run --no-report -p monty-datatest
# coverage for `make test-memory-model-checks`
- run: cargo llvm-cov --no-report -p monty --features memory-model-checks
- run: cargo llvm-cov run --no-report -p monty-datatest --features memory-model-checks
# coverage for `make test-ref-count-return`
- run: cargo llvm-cov --no-report -p monty --features ref-count-return
- run: cargo llvm-cov run --no-report -p monty-datatest --features ref-count-return
# coverage for `make test-type-checking`
- run: cargo llvm-cov --no-report -p monty_type_checking -p monty_typeshed
# subprocess protocol + child mode + worker pool. The pool tests spawn a
# real `monty` binary: build it plainly first (outside the llvm-cov
# target dir) and point the tests at it explicitly.
- run: cargo build -p monty-cli
- run: cargo llvm-cov --no-report -p monty-proto -p monty-cli -p monty-pool
env:
MONTY_TEST_BIN: ${{ github.workspace }}/target/debug/monty
# Generating text report:
- run: cargo llvm-cov report --ignore-filename-regex "$LLVM_COV_IGNORE_FILENAME_REGEX"
# Generate codecov report (use `report` subcommand to avoid recompilation)
- run: cargo llvm-cov report --codecov --output-path=rust-coverage.json --ignore-filename-regex "$LLVM_COV_IGNORE_FILENAME_REGEX"
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: rust-coverage-report
path: rust-coverage.json
if-no-files-found: error
test-python-coverage:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
# no release versioning, see https://github.com/dtolnay/rust-toolchain/issues/180
- uses: dtolnay/rust-toolchain@efa25f7f19611383d5b0ccf2d1c8914531636bf9
with:
toolchain: stable
components: llvm-tools
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
lookup-only: false # zizmor: ignore[cache-poisoning] -- Job does not produce release artifacts and does not have sensitive permissions
cache-on-failure: true
- uses: taiki-e/install-action@1ed3272338f573e042a2e6bca3893aa19f43b47a # v2.71.3
with:
tool: cargo-llvm-cov
- name: set up python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: '3.14'
- uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
with:
enable-cache: true # zizmor: ignore[cache-poisoning] -- Job does not produce release artifacts and does not have sensitive permissions
- run: rustc --version --verbose
- run: python3 -V
- run: uv sync --all-packages --only-dev
# don't use .venv python in CI
- run: rm .cargo/config.toml
# pydantic-monty depends on pydantic-monty-cli; install it from the
# workspace so `maturin develop` can resolve it (plain build, outside
# the llvm-cov env, so the worker binary is not instrumented)
- run: uv run maturin develop --uv -m crates/monty-cli/Cargo.toml
- name: Build and test Python bindings and run pytest with Rust coverage
run: |
set -euxo pipefail
eval "$(cargo llvm-cov show-env --export-prefix)"
cargo llvm-cov clean --workspace
uv run maturin develop --uv -m crates/monty-python/Cargo.toml
uv run --package pydantic-monty --only-dev pytest crates/monty-python/tests
cargo llvm-cov report --ignore-filename-regex "$LLVM_COV_IGNORE_FILENAME_REGEX"
cargo llvm-cov report --codecov --output-path=python-rust-coverage.json --ignore-filename-regex "$LLVM_COV_IGNORE_FILENAME_REGEX"
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: python-rust-coverage-report
path: python-rust-coverage.json
if-no-files-found: error
coverage-upload:
runs-on: ubuntu-latest
needs:
- test-rust
- test-python-coverage
permissions:
# Needed to download coverage report artifacts from the coverage producer jobs.
contents: read
actions: read
# Needed by getsentry/codecov-action when posting the single PR coverage comment.
pull-requests: write
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
pattern: '*-coverage-report'
merge-multiple: true
path: coverage-reports
- name: List coverage reports
run: ls -lh coverage-reports
- uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0
with:
files: coverage-reports/rust-coverage.json,coverage-reports/python-rust-coverage.json
disable_search: true
token: ${{ secrets.CODECOV_TOKEN }}
name: rust-coverage
- uses: getsentry/codecov-action@03112cc3b486a3397dc8d17a58680008721fc86f # 0.3.7
with:
token: ${{ secrets.GITHUB_TOKEN }}
files: coverage-reports/rust-coverage.json,coverage-reports/python-rust-coverage.json
disable-search: true
coverage-format: codecov
post-pr-comment: true
name: rust-coverage
test-python:
name: test python ${{ matrix.python-version }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ['3.10', '3.11', '3.12', '3.13', '3.14']
env:
UV_PYTHON: ${{ matrix.python-version }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
# no release versioning, see https://github.com/dtolnay/rust-toolchain/issues/180
- uses: dtolnay/rust-toolchain@efa25f7f19611383d5b0ccf2d1c8914531636bf9
with:
toolchain: stable
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
lookup-only: false # zizmor: ignore[cache-poisoning] -- Job does not produce release artifacts and does not have sensitive permissions
cache-on-failure: true
- uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
with:
enable-cache: true # zizmor: ignore[cache-poisoning] -- Job does not produce release artifacts and does not have sensitive permissions
- run: uv sync --all-packages --only-dev
- run: make dev-py
- run: make pytest
# also test with a release build
- run: make dev-py-release
- run: make pytest
# test uv run exercise script
- run: uv run crates/monty-python/exercise.py
test-rust-os:
name: test rust on ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- os: macos-latest
- os: windows-latest
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
# no release versioning, see https://github.com/dtolnay/rust-toolchain/issues/180
- uses: dtolnay/rust-toolchain@efa25f7f19611383d5b0ccf2d1c8914531636bf9
with:
toolchain: stable
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
lookup-only: false # zizmor: ignore[cache-poisoning] -- Job does not produce release artifacts and does not have sensitive permissions
cache-on-failure: true
- name: set up python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: '3.14'
# don't use .venv python in CI
- name: Remove cargo config (Unix)
if: runner.os != 'Windows'
run: rm .cargo/config.toml
- name: Remove cargo config (Windows)
if: runner.os == 'Windows'
run: Remove-Item .cargo/config.toml
- run: cargo test -p monty --features memory-model-checks
- run: cargo run -p monty-datatest --features memory-model-checks
- run: cargo build -p monty-cli
- name: Test subprocess crates (Unix)
if: runner.os != 'Windows'
run: cargo test -p monty-proto -p monty-cli -p monty-pool
env:
MONTY_TEST_BIN: ${{ github.workspace }}/target/debug/monty
- name: Test subprocess crates (Windows)
if: runner.os == 'Windows'
run: cargo test -p monty-proto -p monty-cli -p monty-pool
env:
MONTY_TEST_BIN: ${{ github.workspace }}\target\debug\monty.exe
bench-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
# no release versioning, see https://github.com/dtolnay/rust-toolchain/issues/180
- uses: dtolnay/rust-toolchain@efa25f7f19611383d5b0ccf2d1c8914531636bf9
with:
toolchain: stable
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
lookup-only: false # zizmor: ignore[cache-poisoning] -- Job does not produce release artifacts and does not have sensitive permissions
cache-on-failure: true
- name: set up python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: '3.14'
# don't use .venv python in CI
- run: rm .cargo/config.toml
- run: make dev-bench
miri:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
# no release versioning, see https://github.com/dtolnay/rust-toolchain/issues/180
- uses: dtolnay/rust-toolchain@efa25f7f19611383d5b0ccf2d1c8914531636bf9
with:
toolchain: nightly
components: miri
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
lookup-only: false # zizmor: ignore[cache-poisoning] -- Job does not produce release artifacts and does not have sensitive permissions
cache-on-failure: true
# don't use .venv python in CI
- run: rm .cargo/config.toml
- name: Run miri tests
run: make miri
fuzz:
name: fuzz ${{ matrix.target }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
target:
- tokens_input_panic
# disable until https://github.com/astral-sh/ruff/issues/23198 is fixed
# - string_input_panic
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
# no release versioning, see https://github.com/dtolnay/rust-toolchain/issues/180
- uses: dtolnay/rust-toolchain@efa25f7f19611383d5b0ccf2d1c8914531636bf9
with:
toolchain: nightly
- id: cache-rust
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
lookup-only: false # zizmor: ignore[cache-poisoning] -- Job does not produce release artifacts and does not have sensitive permissions
cache-on-failure: true
prefix-key: 'v1-rust-fuzz'
workspaces: 'crates/fuzz -> target'
- if: steps.cache-rust.outputs.cache-hit != 'true'
run: cargo install cargo-fuzz
# don't use .venv python in CI
- run: rm .cargo/config.toml
- name: Run ${{ matrix.target }} fuzzer
run: |
# Use --sanitizer none to avoid ASAN/SanitizerCoverage linking issues on CI
# (undefined __sancov_gen_.* symbols). For short CI runs, we're mainly
# catching panics, not memory bugs.
cargo fuzz run --fuzz-dir crates/fuzz --sanitizer none ${{ matrix.target }} -- -max_total_time=60
# https://github.com/marketplace/actions/alls-green#why used for branch protection checks
check:
if: always()
needs:
- lint
- test-rust
- test-python-coverage
- coverage-upload
- test-rust-os
- test-python
- test-js
- bench-test
- fuzz
runs-on: ubuntu-latest
steps:
- name: Decide whether the needed jobs succeeded or failed
uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # v1.2.2
with:
jobs: ${{ toJSON(needs) }}
# Build source distribution
build-sdist:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: '3.12'
- uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1
with:
command: sdist
args: --out dist
rust-toolchain: stable
working-directory: crates/monty-python
- uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1
with:
command: sdist
args: --out dist
rust-toolchain: stable
working-directory: crates/monty-cli
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: pypi_files-cli-sdist
path: crates/monty-cli/dist
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: pypi_files-sdist
path: crates/monty-python/dist
# Build wheels for exotic architectures (non-PGO)
build:
name: build on ${{ matrix.os }} (${{ matrix.target }} - ${{ matrix.manylinux || 'auto' }})
# only run on push to main, on tags, or if 'Full Build' label is present
if: startsWith(github.ref, 'refs/tags/') || github.ref == 'refs/heads/main' || contains(github.event.pull_request.labels.*.name, 'Full Build') || (github.event_name == 'workflow_dispatch' && inputs.run_release)
strategy:
fail-fast: false
matrix:
include:
# Linux aarch64
- os: linux
target: aarch64
# Linux i686
- os: linux
target: i686
# Linux armv7
- os: linux
target: armv7
# Linux ppc64le
- os: linux
target: ppc64le
# Linux s390x
- os: linux
target: s390x
# Linux x86_64 musl
- os: linux
target: x86_64
manylinux: musllinux_1_1
# Linux aarch64 musl
- os: linux
target: aarch64
manylinux: musllinux_1_1
# macOS x86_64 (Intel)
- os: macos
target: x86_64
# Windows i686
- os: windows
target: i686
runs-on: ${{ (matrix.os == 'linux' && 'ubuntu-latest') || (matrix.os == 'macos' && 'macos-latest') || (matrix.os == 'windows' && 'windows-latest') }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: '3.12'
- name: build wheels
uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1
with:
target: ${{ matrix.target }}
manylinux: ${{ matrix.manylinux || 'auto' }}
args: --release --out dist -i 3.10 3.11 3.12 3.13 3.14
rust-toolchain: stable
docker-options: -e CI
working-directory: crates/monty-python
# the pydantic-monty-cli wheel ships the `monty` worker binary
# (bin bindings, one py3-none wheel per platform). `-i` is required so
# maturin can find a Python to read pyproject.toml — the produced wheel
# is `py3-none-<plat>` and doesn't depend on a specific Python version.
- name: build cli wheel
uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1
with:
target: ${{ matrix.target }}
manylinux: ${{ matrix.manylinux || 'auto' }}
args: --release --out dist -i 3.10 3.11 3.12 3.13 3.14
rust-toolchain: stable
docker-options: -e CI
working-directory: crates/monty-cli
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: pypi_files-${{ matrix.os }}-${{ matrix.target }}-${{ matrix.manylinux || 'manylinux' }}
path: crates/monty-python/dist
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: pypi_files-${{ matrix.os }}-${{ matrix.target }}-${{ matrix.manylinux || 'manylinux' }}-cli
path: crates/monty-cli/dist
# PGO-optimized builds for main platforms
build-pgo:
name: build pgo on ${{ matrix.os }}
# only run on push to main, on tags, or if 'Full Build' label is present
if: startsWith(github.ref, 'refs/tags/') || github.ref == 'refs/heads/main' || contains(github.event.pull_request.labels.*.name, 'Full Build') || (github.event_name == 'workflow_dispatch' && inputs.run_release)
strategy:
fail-fast: false
matrix:
include:
# Linux x86_64 (manylinux)
- os: linux
runs-on: ubuntu-latest
interpreter: 3.10 3.11 3.12 3.13 3.14
# Windows x86_64
- os: windows
runs-on: windows-latest
interpreter: 3.10 3.11 3.12 3.13 3.14
# macOS aarch64 (Apple Silicon)
- os: macos
runs-on: macos-latest
interpreter: 3.10 3.11 3.12 3.13 3.14
runs-on: ${{ matrix.runs-on }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: '3.12'
# no release versioning, see https://github.com/dtolnay/rust-toolchain/issues/180
- uses: dtolnay/rust-toolchain@efa25f7f19611383d5b0ccf2d1c8914531636bf9
with:
toolchain: stable
components: llvm-tools
- name: build PGO wheel
id: pgo
uses: ./.github/actions/build-pgo-wheel
with:
interpreter: ${{ matrix.interpreter }}
rust-toolchain: stable
# the PGO-optimized cli wheel for the main platforms — reuses the
# merged profile data from the build-pgo-wheel action above (which
# exercised an instrumented monty binary via `exercise.py`).
# `-i` is required so maturin can find a Python to read pyproject.toml
# inside the manylinux docker container; the produced wheel is
# `py3-none-<plat>` and doesn't depend on a specific Python version.
- name: build cli wheel
uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1
with:
manylinux: auto
args: --release --out dist -i 3.10 3.11 3.12 3.13 3.14
rust-toolchain: stable
docker-options: -e CI
working-directory: crates/monty-cli
env:
RUSTFLAGS: '-Cprofile-use=${{ github.workspace }}/merged.profdata'
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: pypi_files-${{ matrix.os }}-pgo
path: ${{ steps.pgo.outputs.wheel-dir }}
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: pypi_files-${{ matrix.os }}-pgo-cli
path: crates/monty-cli/dist
# Test wheels on exotic architectures via QEMU
test-builds-arch:
name: test build on ${{ matrix.target }}
needs: [build]
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
target: [aarch64, armv7, s390x, ppc64le]
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
pattern: pypi_files-linux-${{ matrix.target }}-*
merge-multiple: true
path: dist
- uses: uraimo/run-on-arch-action@d94c13912ea685de38fccc1109385b83fd79427d # v3.0.1
name: install & test
with:
arch: ${{ matrix.target }}
distro: ubuntu22.04
dockerRunArgs: --volume "${{ github.workspace }}/dist:/dist"
install: |
apt-get update
apt-get install -y --no-install-recommends python3 python3-pip python3-venv
run: |
ls -lh /dist/
python3 -m venv venv
source venv/bin/activate
python3 -m pip install typing_extensions
python3 -m pip install pydantic-monty pydantic-monty-cli --no-index --no-deps --find-links /dist --force-reinstall
python3 - <<'EOF'
from pydantic_monty import Monty
with Monty() as pool:
with pool.checkout() as session:
assert session.feed_run('1 + 2') == 3
print('worker pool OK')
EOF
# Test wheels on main OS platforms
test-builds-os:
name: test build on ${{ matrix.os }}
needs: [build, build-pgo]
runs-on: ${{ matrix.runs-on }}
strategy:
fail-fast: false
matrix:
include:
- os: linux
runs-on: ubuntu-latest
- os: macos
runs-on: macos-latest
- os: windows
runs-on: windows-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: '3.12'
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
pattern: pypi_files-${{ matrix.os }}-*
merge-multiple: true
path: dist
- run: pip install typing_extensions
- run: pip install pydantic-monty --no-index --no-deps --find-links dist --force-reinstall
- run: python -c "import pydantic_monty; print(pydantic_monty.__version__)"
# the cli wheel built for this platform must serve the worker pools
- run: pip install pydantic-monty-cli --no-index --no-deps --find-links dist --force-reinstall
- name: smoke test worker pools
shell: bash
run: |
python - <<'EOF'
import asyncio
from pydantic_monty import AsyncMonty, Monty
with Monty() as pool:
with pool.checkout() as session:
assert session.feed_run('1 + 2') == 3
async def main():
async with AsyncMonty() as pool:
async with pool.checkout() as session:
assert await session.feed_run('1 + 2') == 3
asyncio.run(main())
print('worker pools OK')
EOF
# Inspect built artifacts
inspect-python-assets:
needs: [build, build-pgo, build-sdist]
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
pattern: pypi_files-*
merge-multiple: true
path: dist
- name: list files
run: |
ls -lhR dist/
ls -1 dist/ | wc -l
echo "Expected ~62 files (5 Python versions × 10 platform variants + 12 cli wheels + sdists)"
- uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
with:
enable-cache: true # zizmor: ignore[cache-poisoning] -- Job does not produce release artifacts and does not have sensitive permissions
- run: uvx twine check --strict dist/*
# Release to PyPI
release-python:
name: release to PyPI
needs: [check, inspect-python-assets, test-builds-arch, test-builds-os]
if: success() && (startsWith(github.ref, 'refs/tags/') || (github.event_name == 'workflow_dispatch' && inputs.run_release))
runs-on: ubuntu-latest
environment:
name: release-python
url: https://pypi.org/project/pydantic-monty/${{ steps.check-version.outputs.VERSION }}
permissions:
id-token: write
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
with:
enable-cache: true # zizmor: ignore[cache-poisoning] -- Job does not produce release artifacts and does not have sensitive permissions
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
pattern: pypi_files-*
merge-multiple: true
path: dist
- id: check-version
uses: samuelcolvin/check-python-version@ee87cddb8049d2694cc03badc8569765a05cef00 # v5
with:
version_file_path: 'Cargo.toml'
- run: ls -lhR dist/
- name: Publish to PyPI
run: 'uv publish --trusted-publishing always dist/*'
test-js:
name: test JS on ${{ matrix.os }} - node@${{ matrix.node }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
node:
- '20'
- '22'
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
# no release versioning, see https://github.com/dtolnay/rust-toolchain/issues/180
- uses: dtolnay/rust-toolchain@efa25f7f19611383d5b0ccf2d1c8914531636bf9
with:
toolchain: stable
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
lookup-only: false # zizmor: ignore[cache-poisoning] -- Job does not produce release artifacts and does not have sensitive permissions
cache-on-failure: true
- name: Setup node
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 # zizmor: ignore[cache-poisoning] -- Job does not produce release artifacts and does not have sensitive permissions
with:
node-version: ${{ matrix.node }}
cache: npm
cache-dependency-path: crates/monty-js/package-lock.json
- name: Install dependencies
run: npm install
working-directory: crates/monty-js
# the workers the JS client drives
- name: Build the monty binary
run: cargo build -p monty-cli
# debug napi build + TypeScript; tests and the smoke test load it
- name: Build
run: npm run build:debug
working-directory: crates/monty-js
- name: Lint
run: npm run lint
working-directory: crates/monty-js
- name: Test
run: npm test
working-directory: crates/monty-js
- name: Smoke test packaging
if: ${{ matrix.os != 'windows-latest' }}
run: npm run smoke-test
working-directory: crates/monty-js
# Release builds of the monty binary AND the napi shared library for the
# npm platform packages (@pydantic/monty-<platform>): each package ships
# the `.node` binding next to the `monty` worker binary.
build-js-binaries:
name: build JS binary - ${{ matrix.settings.triple }}
runs-on: ${{ matrix.settings.host }}
# only run on push to main, on tags, or if 'Full Build' label is present
if: startsWith(github.ref, 'refs/tags/') || github.ref == 'refs/heads/main' || contains(github.event.pull_request.labels.*.name, 'Full Build') || (github.event_name == 'workflow_dispatch' && inputs.run_release)
strategy:
fail-fast: false
matrix:
settings:
- host: macos-latest
target: x86_64-apple-darwin
triple: darwin-x64
- host: macos-latest
target: aarch64-apple-darwin
triple: darwin-arm64
- host: windows-latest
target: x86_64-pc-windows-msvc
triple: win32-x64-msvc
- host: ubuntu-latest
target: x86_64-unknown-linux-gnu
triple: linux-x64-gnu
- host: ubuntu-24.04-arm
target: aarch64-unknown-linux-gnu
triple: linux-arm64-gnu
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
# no release versioning, see https://github.com/dtolnay/rust-toolchain/issues/180
- uses: dtolnay/rust-toolchain@efa25f7f19611383d5b0ccf2d1c8914531636bf9
with:
toolchain: stable
targets: ${{ matrix.settings.target }}
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
lookup-only: false # zizmor: ignore[cache-poisoning] -- Job does not produce release artifacts and does not have sensitive permissions
cache-on-failure: true
prefix-key: v1-rust-js-bin
- name: Setup node
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: 24
package-manager-cache: false
- name: Install node dependencies
run: npm install
working-directory: crates/monty-js
- name: Build the monty binary
run: cargo build --release -p monty-cli --target ${{ matrix.settings.target }}
- name: Build the napi shared library
run: npx napi build --platform --release --esm --target ${{ matrix.settings.target }}
working-directory: crates/monty-js
- name: Upload artifact
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: monty-bin-${{ matrix.settings.triple }}
path: |
target/${{ matrix.settings.target }}/release/monty
target/${{ matrix.settings.target }}/release/monty.exe
crates/monty-js/monty.${{ matrix.settings.triple }}.node
if-no-files-found: error
build-wasm:
name: build wasm
runs-on: ubuntu-latest
# only run on push to main, on tags, or if 'Full Build' label is present
if: startsWith(github.ref, 'refs/tags/') || github.ref == 'refs/heads/main' || contains(github.event.pull_request.labels.*.name, 'Full Build') || (github.event_name == 'workflow_dispatch' && inputs.run_release)
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Setup node
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: 24
package-manager-cache: false
# no release versioning, see https://github.com/dtolnay/rust-toolchain/issues/180
- uses: dtolnay/rust-toolchain@efa25f7f19611383d5b0ccf2d1c8914531636bf9
with:
toolchain: stable
targets: wasm32-wasip1-threads
- name: Cache cargo
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 # zizmor: ignore[cache-poisoning]
with:
path: |
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
~/.napi-rs
.cargo-cache
target/
key: wasm32-wasip1-threads-cargo-ubuntu-latest
# don't use .venv python in CI
- run: rm .cargo/config.toml
- name: Install dependencies
run: npm install
working-directory: crates/monty-js
- name: Build
run: npm run build:wasm
working-directory: crates/monty-js
- name: Upload artifact
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: wasm-bindings
path: |
crates/monty-js/monty.*.wasm
crates/monty-js/browser.js
crates/monty-js/index.js
crates/monty-js/index.d.ts
crates/monty-js/monty.wasi.cjs
crates/monty-js/monty.wasi-browser.js
crates/monty-js/wasi-worker.mjs
crates/monty-js/wasi-worker-browser.mjs
if-no-files-found: error
env:
CARGO_INCREMENTAL: '1'
test-wasm:
name: test wasm
needs:
- build-wasm
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Setup node
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 # zizmor: ignore[cache-poisoning] -- Job does not produce release artifacts and does not have sensitive permissions
with:
node-version: 24
cache: npm
cache-dependency-path: crates/monty-js/package-lock.json
- name: Install dependencies
run: npm install
working-directory: crates/monty-js
- name: Download artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: wasm-bindings
path: crates/monty-js
# only the in-process (wasm_*) specs: the subprocess pool cannot exist
# on wasm, so the pool specs are native-only
- name: Test bindings
run: npx ava "__test__/wasm_*.spec.ts"
env:
NAPI_RS_FORCE_WASI: 1
working-directory: crates/monty-js
release-js:
name: Release to NPM
runs-on: ubuntu-latest
needs:
- check
- inspect-python-assets
- test-js
- build-js-binaries
- test-wasm
if: success() && (startsWith(github.ref, 'refs/tags/') || (github.event_name == 'workflow_dispatch' && inputs.run_release))
permissions:
contents: write
id-token: write
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Setup node
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: 24
package-manager-cache: false
registry-url: 'https://registry.npmjs.org'
# no release versioning, see https://github.com/dtolnay/rust-toolchain/issues/180
- uses: dtolnay/rust-toolchain@efa25f7f19611383d5b0ccf2d1c8914531636bf9
with:
toolchain: stable
- name: Install dependencies
run: npm install
working-directory: crates/monty-js
# generates the loader (index.js/index.d.ts) and dist/; the host .node
# it also builds is not shipped (platform packages carry the .nodes)
- name: Build
run: npm run build
working-directory: crates/monty-js
- name: Create platform package dirs
run: |
npx napi create-npm-dirs
npm run create-platform-packages
working-directory: crates/monty-js
- name: Download binaries into platform packages
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
pattern: monty-bin-*
path: crates/monty-js/artifacts
- name: Download wasm artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: wasm-bindings
path: crates/monty-js/artifacts/wasm
# download-artifact nests artifacts and drops the executable bit;
# arrange each triple's binary + .node into npm/<triple>/ and the wasm
# runtime files into npm/wasm32-wasi/
- name: Arrange binaries
run: |
for dir in artifacts/monty-bin-*; do
triple=${dir#artifacts/monty-bin-}
find "$dir" -name 'monty*' -exec mv {} "npm/$triple/" \;
done
chmod +x npm/*/monty npm/*/monty.exe 2>/dev/null || true
mv artifacts/wasm/monty.wasm32-wasi.wasm npm/wasm32-wasi/
mv artifacts/wasm/monty.wasi.cjs artifacts/wasm/monty.wasi-browser.js npm/wasm32-wasi/
mv artifacts/wasm/wasi-worker.mjs artifacts/wasm/wasi-worker-browser.mjs npm/wasm32-wasi/
ls -lR npm/
working-directory: crates/monty-js
- name: Publish
run: |
if [[ "$GITHUB_REF" =~ ^refs/tags/v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "Publishing stable release"
TAG_ARG=""
else
echo "Publishing pre-release with 'next' tag"
TAG_ARG="--tag next"
fi
for pkg_dir in npm/*/; do
(cd "$pkg_dir" && npm publish --provenance --access public $TAG_ARG)
done
npm publish --provenance --access public $TAG_ARG
working-directory: crates/monty-js