Support multi-level (transitive) closure capture #1918
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |