on: push: branches: [main, release] pull_request: types: [unlabeled, opened, synchronize, reopened] merge_group: workflow_dispatch: name: CI # Cancel previous workflows if they are the same workflow on same ref (branch/tags) # with the same event (push/pull_request) even they are in progress. # This setting will help reduce the number of duplicated workflows. concurrency: group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }} cancel-in-progress: true env: CARGO_ARGS: --no-default-features --features stdlib,importlib,stdio,encodings,sqlite,ssl-rustls,host_env CARGO_ARGS_NO_SSL: --no-default-features --features stdlib,importlib,stdio,encodings,sqlite,host_env # Crates excluded from workspace builds: # - rustpython_wasm: requires wasm target # - rustpython-compiler-source: deprecated # - rustpython-venvlauncher: Windows-only WORKSPACE_EXCLUDES: --exclude rustpython_wasm --exclude rustpython-compiler-source --exclude rustpython-venvlauncher # Python version targeted by the CI. PYTHON_VERSION: "3.14.3" X86_64_PC_WINDOWS_MSVC_OPENSSL_LIB_DIR: C:\Program Files\OpenSSL\lib\VC\x64\MD X86_64_PC_WINDOWS_MSVC_OPENSSL_INCLUDE_DIR: C:\Program Files\OpenSSL\include jobs: rust_tests: if: ${{ !contains(github.event.pull_request.labels.*.name, 'skip:ci') }} env: RUST_BACKTRACE: full name: Run rust tests runs-on: ${{ matrix.os }} timeout-minutes: 45 strategy: matrix: os: [macos-latest, ubuntu-latest, windows-2025] fail-fast: false steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - uses: dtolnay/rust-toolchain@stable with: components: clippy - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 with: save-if: ${{ github.ref == 'refs/heads/main' }} - name: Install macOS dependencies uses: ./.github/actions/install-macos-deps - name: run clippy run: cargo clippy ${{ env.CARGO_ARGS }} --workspace --all-targets ${{ env.WORKSPACE_EXCLUDES }} -- -Dwarnings - name: run rust tests run: cargo test --workspace ${{ env.WORKSPACE_EXCLUDES }} --verbose --features threading ${{ env.CARGO_ARGS }} - name: check compilation without threading run: cargo check ${{ env.CARGO_ARGS }} - name: check compilation without host_env (sandbox mode) run: | cargo check -p rustpython-vm --no-default-features --features compiler cargo check -p rustpython-stdlib --no-default-features --features compiler cargo build --no-default-features --features stdlib,importlib,stdio,encodings,freeze-stdlib if: runner.os == 'Linux' - name: sandbox smoke test run: | target/debug/rustpython extra_tests/snippets/sandbox_smoke.py target/debug/rustpython extra_tests/snippets/stdlib_re.py if: runner.os == 'Linux' - name: Test openssl build run: cargo build --no-default-features --features ssl-openssl if: runner.os == 'Linux' # - name: Install tk-dev for tkinter build # run: sudo apt-get update && sudo apt-get install -y tk-dev # if: runner.os == 'Linux' # - name: Test tkinter build # run: cargo build --features tkinter # if: runner.os == 'Linux' - name: Test example projects run: | cargo run --manifest-path example_projects/barebone/Cargo.toml cargo run --manifest-path example_projects/frozen_stdlib/Cargo.toml if: runner.os == 'Linux' - name: run update_lib tests run: cargo run -- -m unittest discover -s scripts/update_lib/tests -v env: PYTHONPATH: scripts if: runner.os == 'Linux' cargo_check: if: ${{ !contains(github.event.pull_request.labels.*.name, 'skip:ci') }} name: Ensure compilation on various targets runs-on: ${{ matrix.os }} strategy: matrix: include: - os: ubuntu-latest targets: - aarch64-linux-android - i686-unknown-linux-gnu - i686-unknown-linux-musl - wasm32-wasip2 - x86_64-unknown-freebsd dependencies: gcc-multilib: true musl-tools: true - os: ubuntu-latest targets: - aarch64-unknown-linux-gnu dependencies: gcc-aarch64-linux-gnu: true # conflict with `gcc-multilib` - os: macos-latest targets: - aarch64-apple-ios - x86_64-apple-darwin fail-fast: false steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 with: prefix-key: v0-rust-${{ join(matrix.targets, '-') }} save-if: ${{ github.ref == 'refs/heads/main' }} - name: Install dependencies uses: ./.github/actions/install-linux-deps with: ${{ matrix.dependencies || fromJSON('{}') }} - uses: dtolnay/rust-toolchain@stable with: targets: ${{ join(matrix.targets, ',') }} - name: Setup Android NDK if: ${{ contains(matrix.targets, 'aarch64-linux-android') }} id: setup-ndk uses: nttld/setup-ndk@v1 with: ndk-version: r27 add-to-path: true # - name: Prepare repository for redox compilation # run: bash scripts/redox/uncomment-cargo.sh # - name: Check compilation for Redox # uses: coolreader18/redoxer-action@v1 # with: # command: check # args: --ignore-rust-version - name: Check compilation run: | for target in ${{ join(matrix.targets, ' ') }} do echo "::group::${target}" cargo check --target $target ${{ env.CARGO_ARGS_NO_SSL }} echo "::endgroup::" done env: CC_aarch64_linux_android: ${{ steps.setup-ndk.outputs.ndk-path }}/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android24-clang AR_aarch64_linux_android: ${{ steps.setup-ndk.outputs.ndk-path }}/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER: ${{ steps.setup-ndk.outputs.ndk-path }}/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android24-clang snippets_cpython: if: ${{ !contains(github.event.pull_request.labels.*.name, 'skip:ci') }} env: RUST_BACKTRACE: full # PLATFORM_INDEPENDENT_TESTS are tests that do not depend on the underlying OS. # They are currently only run on Linux to speed up the CI. PLATFORM_INDEPENDENT_TESTS: >- test__colorize test_array test_asyncgen test_binop test_bisect test_bool test_bytes test_call test_cmath test_collections test_complex test_contains test_copy test_dataclasses test_decimal test_decorators test_defaultdict test_deque test_dict test_dictcomps test_dictviews test_dis test_enumerate test_exception_variations test_float test_fractions test_genericalias test_genericclass test_grammar test_range test_index test_int test_int_literal test_isinstance test_iter test_iterlen test_itertools test_json test_keyword test_keywordonlyarg test_list test_long test_longexp test_operator test_ordered_dict test_pep646_syntax test_pow test_raise test_richcmp test_scope test_set test_slice test_sort test_string test_string_literals test_strtod test_structseq test_subclassinit test_super test_syntax test_tstring test_tuple test_unary test_unpack test_unpack_ex test_weakref test_yield_from name: Run snippets and cpython tests runs-on: ${{ matrix.os }} strategy: matrix: include: - os: macos-latest extra_test_args: - '-u all' env_polluting_tests: [] skips: [] timeout: 50 - os: ubuntu-latest extra_test_args: - '-u all' env_polluting_tests: [] skips: [] timeout: 60 - os: windows-2025 extra_test_args: [] # TODO: Enable '-u all' env_polluting_tests: [] skips: - test_rlcompleter - test_pathlib # panic by surrogate chars - test_posixpath # OSError: (22, 'The filename, directory name, or volume label syntax is incorrect. (os error 123)') - test_venv # couple of failing tests timeout: 50 fail-fast: false steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 with: save-if: ${{ github.ref == 'refs/heads/main' }} - uses: actions/setup-python@v6.2.0 with: python-version: ${{ env.PYTHON_VERSION }} - name: Install macOS dependencies uses: ./.github/actions/install-macos-deps with: openssl: true - name: build rustpython run: cargo build --release --verbose --features=threading,jit ${{ env.CARGO_ARGS }} - name: run snippets run: python -m pip install -r requirements.txt && pytest -v working-directory: ./extra_tests - name: run cpython platform-independent tests if: runner.os == 'Linux' run: | target/release/rustpython -m test -j 1 -u all --slowest --fail-env-changed --timeout 600 -v ${{ env.PLATFORM_INDEPENDENT_TESTS }} timeout-minutes: 45 env: RUSTPYTHON_SKIP_ENV_POLLUTERS: true - name: run cpython platform-dependent tests run: | target/release/rustpython -m test -j 1 ${{ join(matrix.extra_test_args, ' ') }} --slowest --fail-env-changed --timeout 600 -v -x ${{ env.PLATFORM_INDEPENDENT_TESTS }} ${{ join(matrix.skips, ' ') }} timeout-minutes: ${{ matrix.timeout }} env: RUSTPYTHON_SKIP_ENV_POLLUTERS: true - name: run cpython tests to check if env polluters have stopped polluting shell: bash run: | for thing in ${{ join(matrix.env_polluting_tests, ' ') }}; do for i in $(seq 1 10); do set +e target/release/rustpython -m test -j 1 --slowest --fail-env-changed --timeout 600 -v ${thing} exit_code=$? set -e if [ ${exit_code} -eq 3 ]; then echo "Test ${thing} polluted the environment on attempt ${i}." break fi done if [ ${exit_code} -ne 3 ]; then echo "Test ${thing} is no longer polluting the environment after ${i} attempts!" echo "Please remove ${thing} from matrix.env_polluting_tests in '.github/workflows/ci.yaml'." echo "Please also remove the skip decorators that include the word 'POLLUTERS' in ${thing}." if [ ${exit_code} -ne 0 ]; then echo "Test ${thing} failed with exit code ${exit_code}." echo "Please investigate which test item in ${thing} is failing and either mark it as an expected failure or a skip." fi exit 1 fi done timeout-minutes: 15 - if: runner.os != 'Windows' name: check that --install-pip succeeds run: | mkdir site-packages target/release/rustpython --install-pip ensurepip --user target/release/rustpython -m pip install six - name: Check that ensurepip succeeds. run: | target/release/rustpython -m ensurepip target/release/rustpython -c "import pip" - if: runner.os != 'Windows' name: Check if pip inside venv is functional run: | target/release/rustpython -m venv testvenv testvenv/bin/rustpython -m pip install wheel - name: Check whats_left is not broken shell: bash run: python -I scripts/whats_left.py ${{ env.CARGO_ARGS }} --features jit lint: name: Lint Rust & Python code runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - uses: actions/setup-python@v6.2.0 with: python-version: ${{ env.PYTHON_VERSION }} - name: Check for redundant test patches run: python scripts/check_redundant_patches.py - uses: dtolnay/rust-toolchain@stable with: components: clippy - name: run clippy on wasm run: cargo clippy --manifest-path=crates/wasm/Cargo.toml -- -Dwarnings - name: Ensure docs generate no warnings run: cargo doc --locked - name: Ensure Lib/_opcode_metadata is updated run: | python scripts/generate_opcode_metadata.py if [ -n "$(git status --porcelain)" ]; then exit 1 fi - name: Install ruff uses: astral-sh/ruff-action@4919ec5cf1f49eff0871dbcea0da843445b837e6 # v3.6.1 with: version: "0.15.5" args: "--version" - run: ruff check --diff - run: ruff format --check - name: install prettier run: | yarn global add prettier yarn global bin >> "$GITHUB_PATH" - name: check wasm code with prettier # prettier doesn't handle ignore files very well: https://github.com/prettier/prettier/issues/8506 run: cd wasm && git ls-files -z | xargs -0 prettier --check -u # Keep cspell check as the last step. This is optional test. - name: install extra dictionaries run: npm install @cspell/dict-en_us @cspell/dict-cpp @cspell/dict-python @cspell/dict-rust @cspell/dict-win32 @cspell/dict-shell - name: spell checker uses: streetsidesoftware/cspell-action@v8 with: files: "**/*.rs" incremental_files_only: true miri: if: ${{ !contains(github.event.pull_request.labels.*.name, 'skip:ci') }} name: Run tests under miri runs-on: ubuntu-latest timeout-minutes: 30 env: NIGHTLY_CHANNEL: nightly steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ env.NIGHTLY_CHANNEL }} components: miri - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 with: save-if: ${{ github.ref == 'refs/heads/main' }} - name: Run tests under miri run: cargo +${{ env.NIGHTLY_CHANNEL }} miri test -p rustpython-vm -- miri_test env: # miri-ignore-leaks because the type-object circular reference means that there will always be # a memory leak, at least until we have proper cyclic gc MIRIFLAGS: "-Zmiri-ignore-leaks" wasm: if: ${{ !contains(github.event.pull_request.labels.*.name, 'skip:ci') }} name: Check the WASM package and demo runs-on: ubuntu-latest timeout-minutes: 30 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 with: save-if: ${{ github.ref == 'refs/heads/main' }} - name: install wasm-pack run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh - name: install geckodriver run: | wget https://github.com/mozilla/geckodriver/releases/download/v0.36.0/geckodriver-v0.36.0-linux64.tar.gz mkdir geckodriver tar -xzf geckodriver-v0.36.0-linux64.tar.gz -C geckodriver - uses: actions/setup-python@v6.2.0 with: python-version: ${{ env.PYTHON_VERSION }} - run: python -m pip install -r requirements.txt working-directory: ./wasm/tests - uses: actions/setup-node@v6 with: cache: "npm" cache-dependency-path: "wasm/demo/package-lock.json" - name: run test run: | driver_path="$(pwd)/../../geckodriver" export PATH="$PATH:${driver_path}" npm install npm run test env: NODE_OPTIONS: "--openssl-legacy-provider" working-directory: ./wasm/demo - uses: mwilliamson/setup-wabt-action@v3 with: { wabt-version: "1.0.36" } - name: check wasm32-unknown without js run: | cd example_projects/wasm32_without_js/rustpython-without-js cargo build cd .. if wasm-objdump -xj Import rustpython-without-js/target/wasm32-unknown-unknown/debug/rustpython_without_js.wasm; then echo "ERROR: wasm32-unknown module expects imports from the host environment" >&2 fi cargo run --release --manifest-path wasm-runtime/Cargo.toml rustpython-without-js/target/wasm32-unknown-unknown/debug/rustpython_without_js.wasm - name: build notebook demo if: github.ref == 'refs/heads/release' run: | npm install npm run dist mv dist ../demo/dist/notebook env: NODE_OPTIONS: "--openssl-legacy-provider" working-directory: ./wasm/notebook - name: Deploy demo to Github Pages if: success() && github.ref == 'refs/heads/release' uses: peaceiris/actions-gh-pages@v4 env: ACTIONS_DEPLOY_KEY: ${{ secrets.ACTIONS_DEMO_DEPLOY_KEY }} PUBLISH_DIR: ./wasm/demo/dist EXTERNAL_REPOSITORY: RustPython/demo PUBLISH_BRANCH: master wasm-wasi: if: ${{ !contains(github.event.pull_request.labels.*.name, 'skip:ci') }} name: Run snippets and cpython tests on wasm-wasi runs-on: ubuntu-latest timeout-minutes: 30 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - uses: dtolnay/rust-toolchain@stable with: target: wasm32-wasip1 - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 with: save-if: ${{ github.ref == 'refs/heads/main' }} - name: Setup Wasmer uses: wasmerio/setup-wasmer@v3 - name: Install clang uses: ./.github/actions/install-linux-deps with: clang: true - name: build rustpython run: cargo build --release --target wasm32-wasip1 --features freeze-stdlib,stdlib --verbose - name: run snippets run: wasmer run --dir $(pwd) target/wasm32-wasip1/release/rustpython.wasm -- "$(pwd)/extra_tests/snippets/stdlib_random.py" - name: run cpython unittest run: wasmer run --dir $(pwd) target/wasm32-wasip1/release/rustpython.wasm -- "$(pwd)/Lib/test/test_int.py"