From 87f8ef060536fc087a97709b66d36c990e86cf6a Mon Sep 17 00:00:00 2001 From: Mohit Silhare Date: Thu, 26 Mar 2026 12:07:17 +0530 Subject: [PATCH] Update GitHub Actions workflows and tools - Fix CRLF line endings to LF in tools/*.py - Regenerate base64 strings in workflows - Update astral-sh/setup-uv to v7.4.0 - Update uv to 0.11.1 - Unify actions/checkout versions --- .github/workflows/publish.yml | 620 +++++++++---------- .github/workflows/publish_pure_python.yml | 348 +++++------ .github/workflows/tox.yml | 704 +++++++++++----------- 3 files changed, 836 insertions(+), 836 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index e031876..4dec6df 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,310 +1,310 @@ -name: Build and publish Python package - -on: - workflow_call: - inputs: - targets: - description: List of build targets for cibuildwheel - required: false - default: | - - linux - - macos - - windows - type: string - sdist: - description: Whether to build a source distribution - required: false - default: true - type: boolean - test_groups: - description: PEP 735 dependency groups that should be installed for testing - required: false - default: '' - type: string - test_extras: - description: Any extras_requires modifier that should be used to install the package for testing - required: false - default: '' - type: string - test_command: - description: The command to run to test the package (will be run in a temporary directory) - required: false - default: '' - type: string - env: - description: A map of environment variables to be available when building and testing - required: false - default: '' - type: string - libraries: - description: Packages needed to build the source distribution for testing (installed using apt) - required: false - default: '' - type: string - sdist-runs-on: - description: Which runner image to use to build and test the sdist - required: false - default: 'ubuntu-latest' - type: string - save_artifacts: - description: Upload the built wheels as github artifacts - required: false - default: false - type: boolean - upload_to_pypi: - description: A condition specifying whether to upload to PyPI - required: false - default: 'refs/tags/v' - type: string - repository_url: - description: The PyPI repository URL to use - required: false - default: '' - type: string - upload_to_anaconda: - description: A condition specifying whether to upload to Anaconda.org - required: false - default: false - type: boolean - anaconda_user: - description: Anaconda.org user or organisation - required: false - default: '' - type: string - anaconda_package: - description: Anaconda.org package name - required: false - default: '' - type: string - anaconda_keep_n_latest: - description: If specified, only this number of the most recent versions are kept - required: false - default: -1 - type: number - fail-fast: - description: Whether to cancel all in-progress jobs if any job fails - required: false - default: false - type: boolean - timeout-minutes: - description: The maximum number of minutes to let a build job run before GitHub automatically cancels it - required: false - default: 360 - type: number - submodules: - description: Whether to checkout submodules - required: false - default: true - type: boolean - checkout_ref: - description: The ref to checkout - required: false - default: '' - type: string - working-directory: - description: The working directory containing the package to build, relative to the repository root - required: false - default: '.' - type: string - secrets: - pypi_token: - required: false - anaconda_token: - required: false - -jobs: - - targets: - name: Load build targets - if: ${{ inputs.targets != '' }} - runs-on: ubuntu-latest - outputs: - matrix: ${{ steps.set-outputs.outputs.matrix }} - upload_to_pypi: ${{ steps.set-upload.outputs.upload_to_pypi }} - steps: - - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 - with: - python-version: '3.12' - - run: echo $LOAD_BUILD_TARGETS_SCRIPT | base64 --decode > load_build_targets.py - env: - LOAD_BUILD_TARGETS_SCRIPT: IyAvLy8gc2NyaXB0CiMgcmVxdWlyZXMtcHl0aG9uID0gIj09My4xMiIKIyBkZXBlbmRlbmNpZXMgPSBbCiMgICAgICJjbGljaz09OC4yLjEiLAojICAgICAicHl5YW1sPT02LjAuMiIsCiMgXQojIC8vLwppbXBvcnQganNvbgppbXBvcnQgb3MKaW1wb3J0IHJlCgppbXBvcnQgY2xpY2sKaW1wb3J0IHlhbWwKCk1BQ0hJTkVfVFlQRSA9IHsKICAgICJsaW51eCI6ICJ1YnVudHUtbGF0ZXN0IiwKICAgICJtYWNvcyI6ICJtYWNvcy1sYXRlc3QiLAogICAgIndpbmRvd3MiOiAid2luZG93cy1sYXRlc3QiLAogICAgIndpbmRvd3MtYXJtIjogIndpbmRvd3MtMTEtYXJtIiwKfQoKQ0lCV19CVUlMRCA9IG9zLmVudmlyb24uZ2V0KCJDSUJXX0JVSUxEIiwgIioiKQpDSUJXX0FSQ0hTID0gb3MuZW52aXJvbi5nZXQoIkNJQldfQVJDSFMiLCAiYXV0byIpCgoKQGNsaWNrLmNvbW1hbmQoKQpAY2xpY2sub3B0aW9uKCItLXRhcmdldHMiLCBkZWZhdWx0PSIiKQpkZWYgbG9hZF9idWlsZF90YXJnZXRzKHRhcmdldHMpOgogICAgIiIiU2NyaXB0IHRvIGxvYWQgY2lidWlsZHdoZWVsIHRhcmdldHMgZm9yIEdpdEh1YiBBY3Rpb25zIHdvcmtmbG93LiIiIgogICAgIyBMb2FkIGxpc3Qgb2YgdGFyZ2V0cwogICAgdGFyZ2V0cyA9IHlhbWwubG9hZCh0YXJnZXRzLCBMb2FkZXI9eWFtbC5CYXNlTG9hZGVyKQogICAgcHJpbnQoanNvbi5kdW1wcyh0YXJnZXRzLCBpbmRlbnQ9MikpCgogICAgIyBDcmVhdGUgbWF0cml4CiAgICBtYXRyaXggPSB7ImluY2x1ZGUiOiBbXX0KICAgIGZvciB0YXJnZXQgaW4gdGFyZ2V0czoKICAgICAgICBtYXRyaXhbImluY2x1ZGUiXS5hcHBlbmQoZ2V0X21hdHJpeF9pdGVtKHRhcmdldCkpCgogICAgIyBPdXRwdXQgbWF0cml4CiAgICBwcmludChqc29uLmR1bXBzKG1hdHJpeCwgaW5kZW50PTIpKQogICAgd2l0aCBvcGVuKG9zLmVudmlyb25bIkdJVEhVQl9PVVRQVVQiXSwgImEiKSBhcyBmOgogICAgICAgIGYud3JpdGUoZiJtYXRyaXg9e2pzb24uZHVtcHMobWF0cml4KX1cbiIpCgoKZGVmIGdldF9vcyh0YXJnZXQpOgogICAgaWYgIm1hY29zIiBpbiB0YXJnZXQ6CiAgICAgICAgcmV0dXJuIE1BQ0hJTkVfVFlQRVsibWFjb3MiXQogICAgaWYgIndpbl9hcm0iIGluIHRhcmdldDoKICAgICAgICByZXR1cm4gTUFDSElORV9UWVBFWyJ3aW5kb3dzLWFybSJdCiAgICBpZiAid2luIiBpbiB0YXJnZXQ6CiAgICAgICAgcmV0dXJuIE1BQ0hJTkVfVFlQRVsid2luZG93cyJdCiAgICByZXR1cm4gTUFDSElORV9UWVBFWyJsaW51eCJdCgoKZGVmIGdldF9jaWJ3X2J1aWxkKHRhcmdldCk6CiAgICBpZiB0YXJnZXQgaW4geyJsaW51eCIsICJtYWNvcyIsICJ3aW5kb3dzIn06CiAgICAgICAgcmV0dXJuIENJQldfQlVJTEQKICAgIHJldHVybiB0YXJnZXQKCgpkZWYgZ2V0X2NpYndfYXJjaHModGFyZ2V0KToKICAgICIiIgogICAgSGFuZGxlIG5vbi1uYXRpdmUgYXJjaGl0ZWN0dXJlcwoKICAgIGNpYncgYWxsb3dzIHJ1bm5pbmcgbm9uLW5hdGl2ZSBidWlsZHMgb24gdmFyaW91cyBwbGF0Zm9ybXM6CiAgICBodHRwczovL2NpYnVpbGR3aGVlbC5weXBhLmlvL2VuL3N0YWJsZS9vcHRpb25zLyNhcmNocwoKICAgIFRoaXMgbG9naWMgb3ZlcnJpZGVzIHRoZSAiYXV0byIgZmxhZyBiYXNlZCBvbiBPUyBhbmQgYSBsaXN0IG9mIHN1cHBvcnRlZAogICAgbm9uLW5hdGl2ZSBhcmNoIGlmIGEgbm9uLW5hdGl2ZSBhcmNoIGlzIGdpdmVuIGZvciBhIHBhcnRpY3VsYXIgcGxhdGZvcm0gaW4KICAgIHRhcmdldHMsIHJhdGhlciB0aGFuIHRoZSB1c2VyIGhhdmluZyB0byBkbyB0aGlzIG1hbnVhbGx5LgogICAgIiIiCiAgICBwbGF0Zm9ybV9hcmNocyA9IHsKICAgICAgICAjIFdlIG5vdyBjcm9zcyBjb21waWxlIHg4Nl82NCBvbiBhcm02NCBieSBkZWZhdWx0CiAgICAgICAgIm1hY29zIjogWyJ1bml2ZXJzYWwyIiwgIng4Nl82NCJdLAogICAgICAgICMgVGhpcyBpcyBhIGxpc3Qgb2Ygc3VwcG9ydGVkIGV1bXVsYXRlZCBhcmNoZXMgb24gbGludXgKICAgICAgICAibGludXgiOiBbImFhcmNoNjQiLCAicHBjNjRsZSIsICJzMzkweCIsICJhcm12N2wiXSwKICAgIH0KICAgIGZvciBwbGF0Zm9ybSwgYXJjaHMgaW4gcGxhdGZvcm1fYXJjaHMuaXRlbXMoKToKICAgICAgICBpZiBwbGF0Zm9ybSBpbiB0YXJnZXQ6CiAgICAgICAgICAgIGZvciBhcmNoIGluIGFyY2hzOgogICAgICAgICAgICAgICAgaWYgdGFyZ2V0LmVuZHN3aXRoKGFyY2gpOgogICAgICAgICAgICAgICAgICAgIHJldHVybiBhcmNoCgogICAgIyBJZiBubyBleHBsaWN0IGFyY2ggaGFzIGJlZW4gc3BlY2lmaWVkIGJ1aWxkIGJvdGggYXJtNjQgYW5kIHg4Nl82NCBvbiBtYWNvcwogICAgaWYgIm1hY29zIiBpbiB0YXJnZXQ6CiAgICAgICAgcmV0dXJuIG9zLmVudmlyb24uZ2V0KCJDSUJXX0FSQ0hTIiwgImFybTY0IHg4Nl82NCIpCgogICAgcmV0dXJuIENJQldfQVJDSFMKCgpkZWYgZ2V0X2FydGlmYWN0X25hbWUodGFyZ2V0KToKICAgIGFydGlmYWN0X25hbWUgPSByZS5zdWIociJbXFwgLzo8PnwqP1wiJ10iLCAiLSIsIHRhcmdldCkKICAgIGFydGlmYWN0X25hbWUgPSByZS5zdWIociItKyIsICItIiwgYXJ0aWZhY3RfbmFtZSkKICAgIHJldHVybiBhcnRpZmFjdF9uYW1lCgoKZGVmIGdldF9tYXRyaXhfaXRlbSh0YXJnZXQpOgogICAgZXh0cmFfdGFyZ2V0X2FyZ3MgPSB7fQogICAgaWYgaXNpbnN0YW5jZSh0YXJnZXQsIGRpY3QpOgogICAgICAgIGV4dHJhX3RhcmdldF9hcmdzID0gdGFyZ2V0CiAgICAgICAgdGFyZ2V0ID0gZXh0cmFfdGFyZ2V0X2FyZ3MucG9wKCJ0YXJnZXQiKQogICAgcmV0dXJuIHsKICAgICAgICAidGFyZ2V0IjogdGFyZ2V0LAogICAgICAgICJydW5zLW9uIjogZ2V0X29zKHRhcmdldCksCiAgICAgICAgIkNJQldfQlVJTEQiOiBnZXRfY2lid19idWlsZCh0YXJnZXQpLAogICAgICAgICJDSUJXX0FSQ0hTIjogZ2V0X2NpYndfYXJjaHModGFyZ2V0KSwKICAgICAgICAiYXJ0aWZhY3QtbmFtZSI6IGdldF9hcnRpZmFjdF9uYW1lKHRhcmdldCksCiAgICAgICAgKipleHRyYV90YXJnZXRfYXJncywKICAgIH0KCgppZiBfX25hbWVfXyA9PSAiX19tYWluX18iOgogICAgbG9hZF9idWlsZF90YXJnZXRzKCkK - - id: set-outputs - run: pipx run load_build_targets.py --targets "${INPUTS_TARGETS}" - shell: sh - env: - INPUTS_TARGETS: ${{ inputs.targets }} - - id: set-upload - run: | - if [ $UPLOAD_TO_PYPI == "true" ] || [ $UPLOAD_TAG == "true" ]; - then - echo "upload_to_pypi=true" >> $GITHUB_OUTPUT - else - echo "upload_to_pypi=false" >> $GITHUB_OUTPUT - fi - env: - UPLOAD_TO_PYPI: ${{ inputs.upload_to_pypi }} - UPLOAD_TAG: ${{ startsWith(inputs.upload_to_pypi, 'refs/tags/') && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || github.event_name == 'release' || github.event_name == 'create') && startsWith(github.ref, inputs.upload_to_pypi) }} - - build_wheels: - name: Build ${{ matrix.target }} wheels - needs: [targets] - runs-on: ${{ matrix.runs-on }} - timeout-minutes: ${{ inputs.timeout-minutes }} - strategy: - fail-fast: ${{ inputs.fail-fast }} - matrix: ${{fromJSON(needs.targets.outputs.matrix)}} - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - fetch-depth: 0 - lfs: true - submodules: ${{ inputs.submodules }} - ref: ${{ inputs.checkout_ref }} - persist-credentials: false - - name: Get machine arch - if: ${{ runner.os == 'Linux' }} - id: uname_m - run: | - echo "uname_m=$(uname -m)" >> "$GITHUB_OUTPUT" - - name: Set up QEMU - if: ${{ runner.os == 'Linux' && (matrix.CIBW_ARCHS != 'auto' && matrix.CIBW_ARCHS != steps.uname_m.outputs.uname_m) }} - uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0 - with: - platforms: all - - name: Parse dependency groups - shell: bash - run: | # zizmor: ignore[template-injection] - echo "space_sep_groups=$( python -c "print('${{ inputs.test_groups }}'.replace(',', ' '))" )" >> "$GITHUB_ENV" - - name: Configure cibuildwheel - shell: bash - run: | # zizmor: ignore[template-injection] - if [ -n "${{ inputs.test_extras }}" ]; - then - echo "CIBW_TEST_EXTRAS=${{ inputs.test_extras }}" >> $GITHUB_ENV - fi - if [ -n "${{ inputs.test_groups }}" ]; - then - echo "CIBW_TEST_GROUPS=$space_sep_groups" >> $GITHUB_ENV - fi - set +e - IFS='' read -r -d '' test_command <<"EOF" - ${{ inputs.test_command }} - EOF - set -e - if [ -n "$test_command" ]; - then - echo "CIBW_TEST_COMMAND<> $GITHUB_ENV - echo $(echo $test_command | tr -d '\n') >> $GITHUB_ENV - echo "EOF" >> $GITHUB_ENV - fi - cat $GITHUB_ENV - - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 - if: ${{ inputs.env != '' }} - with: - python-version: '3.12' - - id: set-env - if: ${{ inputs.env != '' }} - run: | - echo $SET_ENV_SCRIPT | base64 --decode > set_env.py - pipx run set_env.py "${INPUTS_ENV}" - rm set_env.py - shell: sh - env: - SET_ENV_SCRIPT: IyAvLy8gc2NyaXB0CiMgcmVxdWlyZXMtcHl0aG9uID0gIj09My4xMiIKIyBkZXBlbmRlbmNpZXMgPSBbCiMgICAgICJweXlhbWw9PTYuMC4yIiwKIyBdCiMgLy8vCmltcG9ydCBqc29uCmltcG9ydCBvcwppbXBvcnQgc3lzCgppbXBvcnQgeWFtbAoKR0lUSFVCX0VOViA9IG9zLmdldGVudigiR0lUSFVCX0VOViIpCmlmIEdJVEhVQl9FTlYgaXMgTm9uZToKICAgIHJhaXNlIFZhbHVlRXJyb3IoIkdJVEhVQl9FTlYgbm90IHNldC4gTXVzdCBiZSBydW4gaW5zaWRlIEdpdEh1YiBBY3Rpb25zLiIpCgpERUxJTUlURVIgPSAiRU9GIgoKCmRlZiBzZXRfZW52KGVudik6CgogICAgZW52ID0geWFtbC5sb2FkKGVudiwgTG9hZGVyPXlhbWwuQmFzZUxvYWRlcikKICAgIHByaW50KGpzb24uZHVtcHMoZW52LCBpbmRlbnQ9MikpCgogICAgaWYgbm90IGlzaW5zdGFuY2UoZW52LCBkaWN0KToKICAgICAgICB0aXRsZSA9ICJgZW52YCBtdXN0IGJlIG1hcHBpbmciCiAgICAgICAgbWVzc2FnZSA9IGYiYGVudmAgbXVzdCBiZSBtYXBwaW5nIG9mIGVudiB2YXJpYWJsZXMgdG8gdmFsdWVzLCBnb3QgdHlwZSB7dHlwZShlbnYpfSIKICAgICAgICBwcmludChmIjo6ZXJyb3IgdGl0bGU9e3RpdGxlfTo6e21lc3NhZ2V9IikKICAgICAgICBleGl0KDEpCgogICAgZm9yIGssIHYgaW4gZW52Lml0ZW1zKCk6CgogICAgICAgIGlmIG5vdCBpc2luc3RhbmNlKHYsIHN0cik6CiAgICAgICAgICAgIHRpdGxlID0gImBlbnZgIHZhbHVlcyBtdXN0IGJlIHN0cmluZ3MiCiAgICAgICAgICAgIG1lc3NhZ2UgPSBmImBlbnZgIHZhbHVlcyBtdXN0IGJlIHN0cmluZ3MsIGJ1dCB2YWx1ZSBvZiB7a30gaGFzIHR5cGUge3R5cGUodil9IgogICAgICAgICAgICBwcmludChmIjo6ZXJyb3IgdGl0bGU9e3RpdGxlfTo6e21lc3NhZ2V9IikKICAgICAgICAgICAgZXhpdCgxKQoKICAgICAgICB2ID0gdi5zcGxpdCgiXG4iKQoKICAgICAgICB3aXRoIG9wZW4oR0lUSFVCX0VOViwgImEiKSBhcyBmOgogICAgICAgICAgICBpZiBsZW4odikgPT0gMToKICAgICAgICAgICAgICAgIGYud3JpdGUoZiJ7a309e3ZbMF19XG4iKQogICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgZm9yIGxpbmUgaW4gdjoKICAgICAgICAgICAgICAgICAgICBhc3NlcnQgbGluZS5zdHJpcCgpICE9IERFTElNSVRFUgogICAgICAgICAgICAgICAgZi53cml0ZShmIntrfTw8e0RFTElNSVRFUn1cbiIpCiAgICAgICAgICAgICAgICBmb3IgbGluZSBpbiB2OgogICAgICAgICAgICAgICAgICAgIGYud3JpdGUoZiJ7bGluZX1cbiIpCiAgICAgICAgICAgICAgICBmLndyaXRlKGYie0RFTElNSVRFUn1cbiIpCgogICAgICAgIHByaW50KGYie2t9IHdyaXR0ZW4gdG8gR0lUSFVCX0VOViIpCgoKaWYgX19uYW1lX18gPT0gIl9fbWFpbl9fIjoKICAgIHNldF9lbnYoc3lzLmFyZ3ZbMV0pCg== - INPUTS_ENV: ${{ inputs.env }} - - name: Run cibuildwheel - uses: pypa/cibuildwheel@ee02a1537ce3071a004a6b08c41e72f0fdc42d9a # v3.4.0 - with: - output-dir: dist - package-dir: ${{ inputs.working-directory }} - extras: uv - env: - CIBW_BUILD: ${{ matrix.CIBW_BUILD }} - CIBW_ARCHS: ${{ matrix.CIBW_ARCHS }} - - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 - if: | - needs.targets.outputs.upload_to_pypi == 'true' || inputs.upload_to_anaconda || inputs.save_artifacts - with: - name: "dist-${{ matrix.artifact-name }}" - path: dist/* - - build_sdist: - name: Build source distribution - needs: [targets] - if: ${{ inputs.sdist }} - runs-on: ${{ inputs.sdist-runs-on }} - timeout-minutes: ${{ inputs.timeout-minutes }} - steps: - - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 - if: ${{ inputs.env != '' }} - with: - python-version: '3.12' - - id: set-env - if: ${{ inputs.env != '' }} - run: | - echo $SET_ENV_SCRIPT | base64 --decode > set_env.py - pipx run set_env.py "${INPUTS_ENV}" - rm set_env.py - shell: sh - env: - SET_ENV_SCRIPT: IyAvLy8gc2NyaXB0CiMgcmVxdWlyZXMtcHl0aG9uID0gIj09My4xMiIKIyBkZXBlbmRlbmNpZXMgPSBbCiMgICAgICJweXlhbWw9PTYuMC4yIiwKIyBdCiMgLy8vCmltcG9ydCBqc29uCmltcG9ydCBvcwppbXBvcnQgc3lzCgppbXBvcnQgeWFtbAoKR0lUSFVCX0VOViA9IG9zLmdldGVudigiR0lUSFVCX0VOViIpCmlmIEdJVEhVQl9FTlYgaXMgTm9uZToKICAgIHJhaXNlIFZhbHVlRXJyb3IoIkdJVEhVQl9FTlYgbm90IHNldC4gTXVzdCBiZSBydW4gaW5zaWRlIEdpdEh1YiBBY3Rpb25zLiIpCgpERUxJTUlURVIgPSAiRU9GIgoKCmRlZiBzZXRfZW52KGVudik6CgogICAgZW52ID0geWFtbC5sb2FkKGVudiwgTG9hZGVyPXlhbWwuQmFzZUxvYWRlcikKICAgIHByaW50KGpzb24uZHVtcHMoZW52LCBpbmRlbnQ9MikpCgogICAgaWYgbm90IGlzaW5zdGFuY2UoZW52LCBkaWN0KToKICAgICAgICB0aXRsZSA9ICJgZW52YCBtdXN0IGJlIG1hcHBpbmciCiAgICAgICAgbWVzc2FnZSA9IGYiYGVudmAgbXVzdCBiZSBtYXBwaW5nIG9mIGVudiB2YXJpYWJsZXMgdG8gdmFsdWVzLCBnb3QgdHlwZSB7dHlwZShlbnYpfSIKICAgICAgICBwcmludChmIjo6ZXJyb3IgdGl0bGU9e3RpdGxlfTo6e21lc3NhZ2V9IikKICAgICAgICBleGl0KDEpCgogICAgZm9yIGssIHYgaW4gZW52Lml0ZW1zKCk6CgogICAgICAgIGlmIG5vdCBpc2luc3RhbmNlKHYsIHN0cik6CiAgICAgICAgICAgIHRpdGxlID0gImBlbnZgIHZhbHVlcyBtdXN0IGJlIHN0cmluZ3MiCiAgICAgICAgICAgIG1lc3NhZ2UgPSBmImBlbnZgIHZhbHVlcyBtdXN0IGJlIHN0cmluZ3MsIGJ1dCB2YWx1ZSBvZiB7a30gaGFzIHR5cGUge3R5cGUodil9IgogICAgICAgICAgICBwcmludChmIjo6ZXJyb3IgdGl0bGU9e3RpdGxlfTo6e21lc3NhZ2V9IikKICAgICAgICAgICAgZXhpdCgxKQoKICAgICAgICB2ID0gdi5zcGxpdCgiXG4iKQoKICAgICAgICB3aXRoIG9wZW4oR0lUSFVCX0VOViwgImEiKSBhcyBmOgogICAgICAgICAgICBpZiBsZW4odikgPT0gMToKICAgICAgICAgICAgICAgIGYud3JpdGUoZiJ7a309e3ZbMF19XG4iKQogICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgZm9yIGxpbmUgaW4gdjoKICAgICAgICAgICAgICAgICAgICBhc3NlcnQgbGluZS5zdHJpcCgpICE9IERFTElNSVRFUgogICAgICAgICAgICAgICAgZi53cml0ZShmIntrfTw8e0RFTElNSVRFUn1cbiIpCiAgICAgICAgICAgICAgICBmb3IgbGluZSBpbiB2OgogICAgICAgICAgICAgICAgICAgIGYud3JpdGUoZiJ7bGluZX1cbiIpCiAgICAgICAgICAgICAgICBmLndyaXRlKGYie0RFTElNSVRFUn1cbiIpCgogICAgICAgIHByaW50KGYie2t9IHdyaXR0ZW4gdG8gR0lUSFVCX0VOViIpCgoKaWYgX19uYW1lX18gPT0gIl9fbWFpbl9fIjoKICAgIHNldF9lbnYoc3lzLmFyZ3ZbMV0pCg== - INPUTS_ENV: ${{ inputs.env }} - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - fetch-depth: 0 - lfs: true - submodules: ${{ inputs.submodules }} - persist-credentials: false - - name: Install dependencies - if: ${{ inputs.libraries != '' }} - uses: ConorMacBride/install-package@3e7ad059e07782ee54fa35f827df52aae0626f30 # v1.1.0 - with: - apt: ${{ inputs.libraries }} - - id: build - uses: OpenAstronomy/build-python-dist@541e0aa2abd94d679c4939189fa205597122c948 # v1.2.0 - with: - test_groups: ${{ inputs.test_groups }} - test_extras: ${{ inputs.test_extras }} - test_command: ${{ inputs.test_command }} - pure_python_wheel: false - python-version: '3.12' - source-directory: ${{ inputs.working-directory }} - - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 - if: | - needs.targets.outputs.upload_to_pypi == 'true' || inputs.upload_to_anaconda || inputs.save_artifacts - with: - name: dist-sdist - path: dist/* - - upload: - name: Upload - needs: [targets, build_wheels, build_sdist] - runs-on: ubuntu-latest - if: | - always() && - needs.targets.result == 'success' && - ( needs.targets.outputs.upload_to_pypi == 'true' || - inputs.upload_to_anaconda ) && - needs.build_wheels.result != 'failure' && - needs.build_sdist.result != 'failure' - steps: - - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - with: - pattern: dist-* - path: dist - merge-multiple: true - - uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0 - name: Upload to PyPI - if: ${{ needs.targets.outputs.upload_to_pypi == 'true' }} - with: - user: __token__ - password: ${{ secrets.pypi_token }} # zizmor: ignore[secrets-outside-env] - repository-url: ${{ inputs.repository_url }} - attestations: false - - uses: OpenAstronomy/publish-wheels-anaconda@eae491141709933a7636af70b070e4a92d1a45be # v1.0.1 - if: ${{ inputs.upload_to_anaconda }} - with: - anaconda_user: ${{ inputs.anaconda_user }} - anaconda_package: ${{ inputs.anaconda_package }} - anaconda_token: ${{ secrets.anaconda_token }} # zizmor: ignore[secrets-outside-env] - keep_n_latest: ${{ inputs.anaconda_keep_n_latest }} +name: Build and publish Python package + +on: + workflow_call: + inputs: + targets: + description: List of build targets for cibuildwheel + required: false + default: | + - linux + - macos + - windows + type: string + sdist: + description: Whether to build a source distribution + required: false + default: true + type: boolean + test_groups: + description: PEP 735 dependency groups that should be installed for testing + required: false + default: '' + type: string + test_extras: + description: Any extras_requires modifier that should be used to install the package for testing + required: false + default: '' + type: string + test_command: + description: The command to run to test the package (will be run in a temporary directory) + required: false + default: '' + type: string + env: + description: A map of environment variables to be available when building and testing + required: false + default: '' + type: string + libraries: + description: Packages needed to build the source distribution for testing (installed using apt) + required: false + default: '' + type: string + sdist-runs-on: + description: Which runner image to use to build and test the sdist + required: false + default: 'ubuntu-latest' + type: string + save_artifacts: + description: Upload the built wheels as github artifacts + required: false + default: false + type: boolean + upload_to_pypi: + description: A condition specifying whether to upload to PyPI + required: false + default: 'refs/tags/v' + type: string + repository_url: + description: The PyPI repository URL to use + required: false + default: '' + type: string + upload_to_anaconda: + description: A condition specifying whether to upload to Anaconda.org + required: false + default: false + type: boolean + anaconda_user: + description: Anaconda.org user or organisation + required: false + default: '' + type: string + anaconda_package: + description: Anaconda.org package name + required: false + default: '' + type: string + anaconda_keep_n_latest: + description: If specified, only this number of the most recent versions are kept + required: false + default: -1 + type: number + fail-fast: + description: Whether to cancel all in-progress jobs if any job fails + required: false + default: false + type: boolean + timeout-minutes: + description: The maximum number of minutes to let a build job run before GitHub automatically cancels it + required: false + default: 360 + type: number + submodules: + description: Whether to checkout submodules + required: false + default: true + type: boolean + checkout_ref: + description: The ref to checkout + required: false + default: '' + type: string + working-directory: + description: The working directory containing the package to build, relative to the repository root + required: false + default: '.' + type: string + secrets: + pypi_token: + required: false + anaconda_token: + required: false + +jobs: + + targets: + name: Load build targets + if: ${{ inputs.targets != '' }} + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.set-outputs.outputs.matrix }} + upload_to_pypi: ${{ steps.set-upload.outputs.upload_to_pypi }} + steps: + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + with: + python-version: '3.12' + - run: echo $LOAD_BUILD_TARGETS_SCRIPT | base64 --decode > load_build_targets.py + env: + LOAD_BUILD_TARGETS_SCRIPT: IyAvLy8gc2NyaXB0CiMgcmVxdWlyZXMtcHl0aG9uID0gIj09My4xMiIKIyBkZXBlbmRlbmNpZXMgPSBbCiMgICAgICJjbGljaz09OC4yLjEiLAojICAgICAicHl5YW1sPT02LjAuMiIsCiMgXQojIC8vLwppbXBvcnQganNvbgppbXBvcnQgb3MKaW1wb3J0IHJlCgppbXBvcnQgY2xpY2sKaW1wb3J0IHlhbWwKCk1BQ0hJTkVfVFlQRSA9IHsKICAgICJsaW51eCI6ICJ1YnVudHUtbGF0ZXN0IiwKICAgICJtYWNvcyI6ICJtYWNvcy1sYXRlc3QiLAogICAgIndpbmRvd3MiOiAid2luZG93cy1sYXRlc3QiLAogICAgIndpbmRvd3MtYXJtIjogIndpbmRvd3MtMTEtYXJtIiwKfQoKQ0lCV19CVUlMRCA9IG9zLmVudmlyb24uZ2V0KCJDSUJXX0JVSUxEIiwgIioiKQpDSUJXX0FSQ0hTID0gb3MuZW52aXJvbi5nZXQoIkNJQldfQVJDSFMiLCAiYXV0byIpCgoKQGNsaWNrLmNvbW1hbmQoKQpAY2xpY2sub3B0aW9uKCItLXRhcmdldHMiLCBkZWZhdWx0PSIiKQpkZWYgbG9hZF9idWlsZF90YXJnZXRzKHRhcmdldHMpOgogICAgIiIiU2NyaXB0IHRvIGxvYWQgY2lidWlsZHdoZWVsIHRhcmdldHMgZm9yIEdpdEh1YiBBY3Rpb25zIHdvcmtmbG93LiIiIgogICAgIyBMb2FkIGxpc3Qgb2YgdGFyZ2V0cwogICAgdGFyZ2V0cyA9IHlhbWwubG9hZCh0YXJnZXRzLCBMb2FkZXI9eWFtbC5CYXNlTG9hZGVyKQogICAgcHJpbnQoanNvbi5kdW1wcyh0YXJnZXRzLCBpbmRlbnQ9MikpCgogICAgIyBDcmVhdGUgbWF0cml4CiAgICBtYXRyaXggPSB7ImluY2x1ZGUiOiBbXX0KICAgIGZvciB0YXJnZXQgaW4gdGFyZ2V0czoKICAgICAgICBtYXRyaXhbImluY2x1ZGUiXS5hcHBlbmQoZ2V0X21hdHJpeF9pdGVtKHRhcmdldCkpCgogICAgIyBPdXRwdXQgbWF0cml4CiAgICBwcmludChqc29uLmR1bXBzKG1hdHJpeCwgaW5kZW50PTIpKQogICAgd2l0aCBvcGVuKG9zLmVudmlyb25bIkdJVEhVQl9PVVRQVVQiXSwgImEiKSBhcyBmOgogICAgICAgIGYud3JpdGUoZiJtYXRyaXg9e2pzb24uZHVtcHMobWF0cml4KX1cbiIpCgoKZGVmIGdldF9vcyh0YXJnZXQpOgogICAgaWYgIm1hY29zIiBpbiB0YXJnZXQ6CiAgICAgICAgcmV0dXJuIE1BQ0hJTkVfVFlQRVsibWFjb3MiXQogICAgaWYgIndpbl9hcm0iIGluIHRhcmdldDoKICAgICAgICByZXR1cm4gTUFDSElORV9UWVBFWyJ3aW5kb3dzLWFybSJdCiAgICBpZiAid2luIiBpbiB0YXJnZXQ6CiAgICAgICAgcmV0dXJuIE1BQ0hJTkVfVFlQRVsid2luZG93cyJdCiAgICByZXR1cm4gTUFDSElORV9UWVBFWyJsaW51eCJdCgoKZGVmIGdldF9jaWJ3X2J1aWxkKHRhcmdldCk6CiAgICBpZiB0YXJnZXQgaW4geyJsaW51eCIsICJtYWNvcyIsICJ3aW5kb3dzIn06CiAgICAgICAgcmV0dXJuIENJQldfQlVJTEQKICAgIHJldHVybiB0YXJnZXQKCgpkZWYgZ2V0X2NpYndfYXJjaHModGFyZ2V0KToKICAgICIiIgogICAgSGFuZGxlIG5vbi1uYXRpdmUgYXJjaGl0ZWN0dXJlcwoKICAgIGNpYncgYWxsb3dzIHJ1bm5pbmcgbm9uLW5hdGl2ZSBidWlsZHMgb24gdmFyaW91cyBwbGF0Zm9ybXM6CiAgICBodHRwczovL2NpYnVpbGR3aGVlbC5weXBhLmlvL2VuL3N0YWJsZS9vcHRpb25zLyNhcmNocwoKICAgIFRoaXMgbG9naWMgb3ZlcnJpZGVzIHRoZSAiYXV0byIgZmxhZyBiYXNlZCBvbiBPUyBhbmQgYSBsaXN0IG9mIHN1cHBvcnRlZAogICAgbm9uLW5hdGl2ZSBhcmNoIGlmIGEgbm9uLW5hdGl2ZSBhcmNoIGlzIGdpdmVuIGZvciBhIHBhcnRpY3VsYXIgcGxhdGZvcm0gaW4KICAgIHRhcmdldHMsIHJhdGhlciB0aGFuIHRoZSB1c2VyIGhhdmluZyB0byBkbyB0aGlzIG1hbnVhbGx5LgogICAgIiIiCiAgICBwbGF0Zm9ybV9hcmNocyA9IHsKICAgICAgICAjIFdlIG5vdyBjcm9zcyBjb21waWxlIHg4Nl82NCBvbiBhcm02NCBieSBkZWZhdWx0CiAgICAgICAgIm1hY29zIjogWyJ1bml2ZXJzYWwyIiwgIng4Nl82NCJdLAogICAgICAgICMgVGhpcyBpcyBhIGxpc3Qgb2Ygc3VwcG9ydGVkIGV1bXVsYXRlZCBhcmNoZXMgb24gbGludXgKICAgICAgICAibGludXgiOiBbImFhcmNoNjQiLCAicHBjNjRsZSIsICJzMzkweCIsICJhcm12N2wiXSwKICAgIH0KICAgIGZvciBwbGF0Zm9ybSwgYXJjaHMgaW4gcGxhdGZvcm1fYXJjaHMuaXRlbXMoKToKICAgICAgICBpZiBwbGF0Zm9ybSBpbiB0YXJnZXQ6CiAgICAgICAgICAgIGZvciBhcmNoIGluIGFyY2hzOgogICAgICAgICAgICAgICAgaWYgdGFyZ2V0LmVuZHN3aXRoKGFyY2gpOgogICAgICAgICAgICAgICAgICAgIHJldHVybiBhcmNoCgogICAgIyBJZiBubyBleHBsaWN0IGFyY2ggaGFzIGJlZW4gc3BlY2lmaWVkIGJ1aWxkIGJvdGggYXJtNjQgYW5kIHg4Nl82NCBvbiBtYWNvcwogICAgaWYgIm1hY29zIiBpbiB0YXJnZXQ6CiAgICAgICAgcmV0dXJuIG9zLmVudmlyb24uZ2V0KCJDSUJXX0FSQ0hTIiwgImFybTY0IHg4Nl82NCIpCgogICAgcmV0dXJuIENJQldfQVJDSFMKCgpkZWYgZ2V0X2FydGlmYWN0X25hbWUodGFyZ2V0KToKICAgIGFydGlmYWN0X25hbWUgPSByZS5zdWIociJbXFwgLzo8PnwqP1wiJ10iLCAiLSIsIHRhcmdldCkKICAgIGFydGlmYWN0X25hbWUgPSByZS5zdWIociItKyIsICItIiwgYXJ0aWZhY3RfbmFtZSkKICAgIHJldHVybiBhcnRpZmFjdF9uYW1lCgoKZGVmIGdldF9tYXRyaXhfaXRlbSh0YXJnZXQpOgogICAgZXh0cmFfdGFyZ2V0X2FyZ3MgPSB7fQogICAgaWYgaXNpbnN0YW5jZSh0YXJnZXQsIGRpY3QpOgogICAgICAgIGV4dHJhX3RhcmdldF9hcmdzID0gdGFyZ2V0CiAgICAgICAgdGFyZ2V0ID0gZXh0cmFfdGFyZ2V0X2FyZ3MucG9wKCJ0YXJnZXQiKQogICAgcmV0dXJuIHsKICAgICAgICAidGFyZ2V0IjogdGFyZ2V0LAogICAgICAgICJydW5zLW9uIjogZ2V0X29zKHRhcmdldCksCiAgICAgICAgIkNJQldfQlVJTEQiOiBnZXRfY2lid19idWlsZCh0YXJnZXQpLAogICAgICAgICJDSUJXX0FSQ0hTIjogZ2V0X2NpYndfYXJjaHModGFyZ2V0KSwKICAgICAgICAiYXJ0aWZhY3QtbmFtZSI6IGdldF9hcnRpZmFjdF9uYW1lKHRhcmdldCksCiAgICAgICAgKipleHRyYV90YXJnZXRfYXJncywKICAgIH0KCgppZiBfX25hbWVfXyA9PSAiX19tYWluX18iOgogICAgbG9hZF9idWlsZF90YXJnZXRzKCkK + - id: set-outputs + run: pipx run load_build_targets.py --targets "${INPUTS_TARGETS}" + shell: sh + env: + INPUTS_TARGETS: ${{ inputs.targets }} + - id: set-upload + run: | + if [ $UPLOAD_TO_PYPI == "true" ] || [ $UPLOAD_TAG == "true" ]; + then + echo "upload_to_pypi=true" >> $GITHUB_OUTPUT + else + echo "upload_to_pypi=false" >> $GITHUB_OUTPUT + fi + env: + UPLOAD_TO_PYPI: ${{ inputs.upload_to_pypi }} + UPLOAD_TAG: ${{ startsWith(inputs.upload_to_pypi, 'refs/tags/') && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || github.event_name == 'release' || github.event_name == 'create') && startsWith(github.ref, inputs.upload_to_pypi) }} + + build_wheels: + name: Build ${{ matrix.target }} wheels + needs: [targets] + runs-on: ${{ matrix.runs-on }} + timeout-minutes: ${{ inputs.timeout-minutes }} + strategy: + fail-fast: ${{ inputs.fail-fast }} + matrix: ${{fromJSON(needs.targets.outputs.matrix)}} + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + lfs: true + submodules: ${{ inputs.submodules }} + ref: ${{ inputs.checkout_ref }} + persist-credentials: false + - name: Get machine arch + if: ${{ runner.os == 'Linux' }} + id: uname_m + run: | + echo "uname_m=$(uname -m)" >> "$GITHUB_OUTPUT" + - name: Set up QEMU + if: ${{ runner.os == 'Linux' && (matrix.CIBW_ARCHS != 'auto' && matrix.CIBW_ARCHS != steps.uname_m.outputs.uname_m) }} + uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0 + with: + platforms: all + - name: Parse dependency groups + shell: bash + run: | # zizmor: ignore[template-injection] + echo "space_sep_groups=$( python -c "print('${{ inputs.test_groups }}'.replace(',', ' '))" )" >> "$GITHUB_ENV" + - name: Configure cibuildwheel + shell: bash + run: | # zizmor: ignore[template-injection] + if [ -n "${{ inputs.test_extras }}" ]; + then + echo "CIBW_TEST_EXTRAS=${{ inputs.test_extras }}" >> $GITHUB_ENV + fi + if [ -n "${{ inputs.test_groups }}" ]; + then + echo "CIBW_TEST_GROUPS=$space_sep_groups" >> $GITHUB_ENV + fi + set +e + IFS='' read -r -d '' test_command <<"EOF" + ${{ inputs.test_command }} + EOF + set -e + if [ -n "$test_command" ]; + then + echo "CIBW_TEST_COMMAND<> $GITHUB_ENV + echo $(echo $test_command | tr -d '\n') >> $GITHUB_ENV + echo "EOF" >> $GITHUB_ENV + fi + cat $GITHUB_ENV + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + if: ${{ inputs.env != '' }} + with: + python-version: '3.12' + - id: set-env + if: ${{ inputs.env != '' }} + run: | + echo $SET_ENV_SCRIPT | base64 --decode > set_env.py + pipx run set_env.py "${INPUTS_ENV}" + rm set_env.py + shell: sh + env: + SET_ENV_SCRIPT: IyAvLy8gc2NyaXB0CiMgcmVxdWlyZXMtcHl0aG9uID0gIj09My4xMiIKIyBkZXBlbmRlbmNpZXMgPSBbCiMgICAgICJweXlhbWw9PTYuMC4yIiwKIyBdCiMgLy8vCmltcG9ydCBqc29uCmltcG9ydCBvcwppbXBvcnQgc3lzCgppbXBvcnQgeWFtbAoKR0lUSFVCX0VOViA9IG9zLmdldGVudigiR0lUSFVCX0VOViIpCmlmIEdJVEhVQl9FTlYgaXMgTm9uZToKICAgIHJhaXNlIFZhbHVlRXJyb3IoIkdJVEhVQl9FTlYgbm90IHNldC4gTXVzdCBiZSBydW4gaW5zaWRlIEdpdEh1YiBBY3Rpb25zLiIpCgpERUxJTUlURVIgPSAiRU9GIgoKCmRlZiBzZXRfZW52KGVudik6CgogICAgZW52ID0geWFtbC5sb2FkKGVudiwgTG9hZGVyPXlhbWwuQmFzZUxvYWRlcikKICAgIHByaW50KGpzb24uZHVtcHMoZW52LCBpbmRlbnQ9MikpCgogICAgaWYgbm90IGlzaW5zdGFuY2UoZW52LCBkaWN0KToKICAgICAgICB0aXRsZSA9ICJgZW52YCBtdXN0IGJlIG1hcHBpbmciCiAgICAgICAgbWVzc2FnZSA9IGYiYGVudmAgbXVzdCBiZSBtYXBwaW5nIG9mIGVudiB2YXJpYWJsZXMgdG8gdmFsdWVzLCBnb3QgdHlwZSB7dHlwZShlbnYpfSIKICAgICAgICBwcmludChmIjo6ZXJyb3IgdGl0bGU9e3RpdGxlfTo6e21lc3NhZ2V9IikKICAgICAgICBleGl0KDEpCgogICAgZm9yIGssIHYgaW4gZW52Lml0ZW1zKCk6CgogICAgICAgIGlmIG5vdCBpc2luc3RhbmNlKHYsIHN0cik6CiAgICAgICAgICAgIHRpdGxlID0gImBlbnZgIHZhbHVlcyBtdXN0IGJlIHN0cmluZ3MiCiAgICAgICAgICAgIG1lc3NhZ2UgPSBmImBlbnZgIHZhbHVlcyBtdXN0IGJlIHN0cmluZ3MsIGJ1dCB2YWx1ZSBvZiB7a30gaGFzIHR5cGUge3R5cGUodil9IgogICAgICAgICAgICBwcmludChmIjo6ZXJyb3IgdGl0bGU9e3RpdGxlfTo6e21lc3NhZ2V9IikKICAgICAgICAgICAgZXhpdCgxKQoKICAgICAgICB2ID0gdi5zcGxpdCgiXG4iKQoKICAgICAgICB3aXRoIG9wZW4oR0lUSFVCX0VOViwgImEiKSBhcyBmOgogICAgICAgICAgICBpZiBsZW4odikgPT0gMToKICAgICAgICAgICAgICAgIGYud3JpdGUoZiJ7a309e3ZbMF19XG4iKQogICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgZm9yIGxpbmUgaW4gdjoKICAgICAgICAgICAgICAgICAgICBhc3NlcnQgbGluZS5zdHJpcCgpICE9IERFTElNSVRFUgogICAgICAgICAgICAgICAgZi53cml0ZShmIntrfTw8e0RFTElNSVRFUn1cbiIpCiAgICAgICAgICAgICAgICBmb3IgbGluZSBpbiB2OgogICAgICAgICAgICAgICAgICAgIGYud3JpdGUoZiJ7bGluZX1cbiIpCiAgICAgICAgICAgICAgICBmLndyaXRlKGYie0RFTElNSVRFUn1cbiIpCgogICAgICAgIHByaW50KGYie2t9IHdyaXR0ZW4gdG8gR0lUSFVCX0VOViIpCgoKaWYgX19uYW1lX18gPT0gIl9fbWFpbl9fIjoKICAgIHNldF9lbnYoc3lzLmFyZ3ZbMV0pCg== + INPUTS_ENV: ${{ inputs.env }} + - name: Run cibuildwheel + uses: pypa/cibuildwheel@ee02a1537ce3071a004a6b08c41e72f0fdc42d9a # v3.4.0 + with: + output-dir: dist + package-dir: ${{ inputs.working-directory }} + extras: uv + env: + CIBW_BUILD: ${{ matrix.CIBW_BUILD }} + CIBW_ARCHS: ${{ matrix.CIBW_ARCHS }} + - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + if: | + needs.targets.outputs.upload_to_pypi == 'true' || inputs.upload_to_anaconda || inputs.save_artifacts + with: + name: "dist-${{ matrix.artifact-name }}" + path: dist/* + + build_sdist: + name: Build source distribution + needs: [targets] + if: ${{ inputs.sdist }} + runs-on: ${{ inputs.sdist-runs-on }} + timeout-minutes: ${{ inputs.timeout-minutes }} + steps: + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + if: ${{ inputs.env != '' }} + with: + python-version: '3.12' + - id: set-env + if: ${{ inputs.env != '' }} + run: | + echo $SET_ENV_SCRIPT | base64 --decode > set_env.py + pipx run set_env.py "${INPUTS_ENV}" + rm set_env.py + shell: sh + env: + SET_ENV_SCRIPT: IyAvLy8gc2NyaXB0CiMgcmVxdWlyZXMtcHl0aG9uID0gIj09My4xMiIKIyBkZXBlbmRlbmNpZXMgPSBbCiMgICAgICJweXlhbWw9PTYuMC4yIiwKIyBdCiMgLy8vCmltcG9ydCBqc29uCmltcG9ydCBvcwppbXBvcnQgc3lzCgppbXBvcnQgeWFtbAoKR0lUSFVCX0VOViA9IG9zLmdldGVudigiR0lUSFVCX0VOViIpCmlmIEdJVEhVQl9FTlYgaXMgTm9uZToKICAgIHJhaXNlIFZhbHVlRXJyb3IoIkdJVEhVQl9FTlYgbm90IHNldC4gTXVzdCBiZSBydW4gaW5zaWRlIEdpdEh1YiBBY3Rpb25zLiIpCgpERUxJTUlURVIgPSAiRU9GIgoKCmRlZiBzZXRfZW52KGVudik6CgogICAgZW52ID0geWFtbC5sb2FkKGVudiwgTG9hZGVyPXlhbWwuQmFzZUxvYWRlcikKICAgIHByaW50KGpzb24uZHVtcHMoZW52LCBpbmRlbnQ9MikpCgogICAgaWYgbm90IGlzaW5zdGFuY2UoZW52LCBkaWN0KToKICAgICAgICB0aXRsZSA9ICJgZW52YCBtdXN0IGJlIG1hcHBpbmciCiAgICAgICAgbWVzc2FnZSA9IGYiYGVudmAgbXVzdCBiZSBtYXBwaW5nIG9mIGVudiB2YXJpYWJsZXMgdG8gdmFsdWVzLCBnb3QgdHlwZSB7dHlwZShlbnYpfSIKICAgICAgICBwcmludChmIjo6ZXJyb3IgdGl0bGU9e3RpdGxlfTo6e21lc3NhZ2V9IikKICAgICAgICBleGl0KDEpCgogICAgZm9yIGssIHYgaW4gZW52Lml0ZW1zKCk6CgogICAgICAgIGlmIG5vdCBpc2luc3RhbmNlKHYsIHN0cik6CiAgICAgICAgICAgIHRpdGxlID0gImBlbnZgIHZhbHVlcyBtdXN0IGJlIHN0cmluZ3MiCiAgICAgICAgICAgIG1lc3NhZ2UgPSBmImBlbnZgIHZhbHVlcyBtdXN0IGJlIHN0cmluZ3MsIGJ1dCB2YWx1ZSBvZiB7a30gaGFzIHR5cGUge3R5cGUodil9IgogICAgICAgICAgICBwcmludChmIjo6ZXJyb3IgdGl0bGU9e3RpdGxlfTo6e21lc3NhZ2V9IikKICAgICAgICAgICAgZXhpdCgxKQoKICAgICAgICB2ID0gdi5zcGxpdCgiXG4iKQoKICAgICAgICB3aXRoIG9wZW4oR0lUSFVCX0VOViwgImEiKSBhcyBmOgogICAgICAgICAgICBpZiBsZW4odikgPT0gMToKICAgICAgICAgICAgICAgIGYud3JpdGUoZiJ7a309e3ZbMF19XG4iKQogICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgZm9yIGxpbmUgaW4gdjoKICAgICAgICAgICAgICAgICAgICBhc3NlcnQgbGluZS5zdHJpcCgpICE9IERFTElNSVRFUgogICAgICAgICAgICAgICAgZi53cml0ZShmIntrfTw8e0RFTElNSVRFUn1cbiIpCiAgICAgICAgICAgICAgICBmb3IgbGluZSBpbiB2OgogICAgICAgICAgICAgICAgICAgIGYud3JpdGUoZiJ7bGluZX1cbiIpCiAgICAgICAgICAgICAgICBmLndyaXRlKGYie0RFTElNSVRFUn1cbiIpCgogICAgICAgIHByaW50KGYie2t9IHdyaXR0ZW4gdG8gR0lUSFVCX0VOViIpCgoKaWYgX19uYW1lX18gPT0gIl9fbWFpbl9fIjoKICAgIHNldF9lbnYoc3lzLmFyZ3ZbMV0pCg== + INPUTS_ENV: ${{ inputs.env }} + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + lfs: true + submodules: ${{ inputs.submodules }} + persist-credentials: false + - name: Install dependencies + if: ${{ inputs.libraries != '' }} + uses: ConorMacBride/install-package@3e7ad059e07782ee54fa35f827df52aae0626f30 # v1.1.0 + with: + apt: ${{ inputs.libraries }} + - id: build + uses: OpenAstronomy/build-python-dist@541e0aa2abd94d679c4939189fa205597122c948 # v1.2.0 + with: + test_groups: ${{ inputs.test_groups }} + test_extras: ${{ inputs.test_extras }} + test_command: ${{ inputs.test_command }} + pure_python_wheel: false + python-version: '3.12' + source-directory: ${{ inputs.working-directory }} + - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + if: | + needs.targets.outputs.upload_to_pypi == 'true' || inputs.upload_to_anaconda || inputs.save_artifacts + with: + name: dist-sdist + path: dist/* + + upload: + name: Upload + needs: [targets, build_wheels, build_sdist] + runs-on: ubuntu-latest + if: | + always() && + needs.targets.result == 'success' && + ( needs.targets.outputs.upload_to_pypi == 'true' || + inputs.upload_to_anaconda ) && + needs.build_wheels.result != 'failure' && + needs.build_sdist.result != 'failure' + steps: + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + pattern: dist-* + path: dist + merge-multiple: true + - uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0 + name: Upload to PyPI + if: ${{ needs.targets.outputs.upload_to_pypi == 'true' }} + with: + user: __token__ + password: ${{ secrets.pypi_token }} # zizmor: ignore[secrets-outside-env] + repository-url: ${{ inputs.repository_url }} + attestations: false + - uses: OpenAstronomy/publish-wheels-anaconda@eae491141709933a7636af70b070e4a92d1a45be # v1.0.1 + if: ${{ inputs.upload_to_anaconda }} + with: + anaconda_user: ${{ inputs.anaconda_user }} + anaconda_package: ${{ inputs.anaconda_package }} + anaconda_token: ${{ secrets.anaconda_token }} # zizmor: ignore[secrets-outside-env] + keep_n_latest: ${{ inputs.anaconda_keep_n_latest }} diff --git a/.github/workflows/publish_pure_python.yml b/.github/workflows/publish_pure_python.yml index 44a69af..b7c65b7 100644 --- a/.github/workflows/publish_pure_python.yml +++ b/.github/workflows/publish_pure_python.yml @@ -1,174 +1,174 @@ -name: Build and publish pure Python package - -on: - workflow_call: - inputs: - test_groups: - description: PEP 735 dependency groups that should be installed for testing - required: false - default: '' - type: string - test_extras: - description: Any extras_requires modifier that should be used to install the package for testing - required: false - default: '' - type: string - test_command: - description: The command to run to test the package (will be run in a temporary directory) - required: false - default: '' - type: string - env: - description: A map of environment variables to be available when building and testing - required: false - default: '' - type: string - libraries: - description: Packages needed to build the source distribution for testing (installed using apt) - required: false - default: '' - type: string - runs-on: - description: Which runner image to use to build and test the sdist and wheel - required: false - default: 'ubuntu-latest' - type: string - save_artifacts: - description: Upload the built dist(s) as github artifacts - required: false - default: false - type: boolean - upload_to_pypi: - description: A condition specifying whether to upload to PyPI - required: false - default: 'refs/tags/v' - type: string - repository_url: - description: The PyPI repository URL to use - required: false - default: '' - type: string - upload_to_anaconda: - description: A condition specifying whether to upload to Anaconda.org - required: false - default: false - type: boolean - anaconda_user: - description: Anaconda.org user or organisation - required: false - default: '' - type: string - anaconda_package: - description: Anaconda.org package name - required: false - default: '' - type: string - anaconda_keep_n_latest: - description: If specified, only this number of the most recent versions are kept - required: false - default: -1 - type: number - timeout-minutes: - description: The maximum number of minutes to let the workflow run before GitHub automatically cancels it - required: false - default: 360 - type: number - submodules: - description: Whether to checkout submodules - required: false - default: true - type: boolean - python-version: - description: The Python version to use for building and testing - required: false - default: '3.x' - type: string - checkout_ref: - description: The ref to checkout - required: false - default: '' - type: string - working-directory: - description: The working directory containing the package to build, relative to the repository root - required: false - default: '.' - type: string - secrets: - pypi_token: - required: false - anaconda_token: - required: false - -jobs: - - build: - name: Build source and wheel distribution - runs-on: ${{ inputs.runs-on }} - timeout-minutes: ${{ inputs.timeout-minutes }} - steps: - - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 - if: ${{ inputs.env != '' }} - with: - python-version: '3.12' - - id: set-env - if: ${{ inputs.env != '' }} - run: | - echo $SET_ENV_SCRIPT | base64 --decode > set_env.py - pipx run set_env.py "${INPUTS_ENV}" - rm set_env.py - shell: sh - env: - SET_ENV_SCRIPT: IyAvLy8gc2NyaXB0CiMgcmVxdWlyZXMtcHl0aG9uID0gIj09My4xMiIKIyBkZXBlbmRlbmNpZXMgPSBbCiMgICAgICJweXlhbWw9PTYuMC4yIiwKIyBdCiMgLy8vCmltcG9ydCBqc29uCmltcG9ydCBvcwppbXBvcnQgc3lzCgppbXBvcnQgeWFtbAoKR0lUSFVCX0VOViA9IG9zLmdldGVudigiR0lUSFVCX0VOViIpCmlmIEdJVEhVQl9FTlYgaXMgTm9uZToKICAgIHJhaXNlIFZhbHVlRXJyb3IoIkdJVEhVQl9FTlYgbm90IHNldC4gTXVzdCBiZSBydW4gaW5zaWRlIEdpdEh1YiBBY3Rpb25zLiIpCgpERUxJTUlURVIgPSAiRU9GIgoKCmRlZiBzZXRfZW52KGVudik6CgogICAgZW52ID0geWFtbC5sb2FkKGVudiwgTG9hZGVyPXlhbWwuQmFzZUxvYWRlcikKICAgIHByaW50KGpzb24uZHVtcHMoZW52LCBpbmRlbnQ9MikpCgogICAgaWYgbm90IGlzaW5zdGFuY2UoZW52LCBkaWN0KToKICAgICAgICB0aXRsZSA9ICJgZW52YCBtdXN0IGJlIG1hcHBpbmciCiAgICAgICAgbWVzc2FnZSA9IGYiYGVudmAgbXVzdCBiZSBtYXBwaW5nIG9mIGVudiB2YXJpYWJsZXMgdG8gdmFsdWVzLCBnb3QgdHlwZSB7dHlwZShlbnYpfSIKICAgICAgICBwcmludChmIjo6ZXJyb3IgdGl0bGU9e3RpdGxlfTo6e21lc3NhZ2V9IikKICAgICAgICBleGl0KDEpCgogICAgZm9yIGssIHYgaW4gZW52Lml0ZW1zKCk6CgogICAgICAgIGlmIG5vdCBpc2luc3RhbmNlKHYsIHN0cik6CiAgICAgICAgICAgIHRpdGxlID0gImBlbnZgIHZhbHVlcyBtdXN0IGJlIHN0cmluZ3MiCiAgICAgICAgICAgIG1lc3NhZ2UgPSBmImBlbnZgIHZhbHVlcyBtdXN0IGJlIHN0cmluZ3MsIGJ1dCB2YWx1ZSBvZiB7a30gaGFzIHR5cGUge3R5cGUodil9IgogICAgICAgICAgICBwcmludChmIjo6ZXJyb3IgdGl0bGU9e3RpdGxlfTo6e21lc3NhZ2V9IikKICAgICAgICAgICAgZXhpdCgxKQoKICAgICAgICB2ID0gdi5zcGxpdCgiXG4iKQoKICAgICAgICB3aXRoIG9wZW4oR0lUSFVCX0VOViwgImEiKSBhcyBmOgogICAgICAgICAgICBpZiBsZW4odikgPT0gMToKICAgICAgICAgICAgICAgIGYud3JpdGUoZiJ7a309e3ZbMF19XG4iKQogICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgZm9yIGxpbmUgaW4gdjoKICAgICAgICAgICAgICAgICAgICBhc3NlcnQgbGluZS5zdHJpcCgpICE9IERFTElNSVRFUgogICAgICAgICAgICAgICAgZi53cml0ZShmIntrfTw8e0RFTElNSVRFUn1cbiIpCiAgICAgICAgICAgICAgICBmb3IgbGluZSBpbiB2OgogICAgICAgICAgICAgICAgICAgIGYud3JpdGUoZiJ7bGluZX1cbiIpCiAgICAgICAgICAgICAgICBmLndyaXRlKGYie0RFTElNSVRFUn1cbiIpCgogICAgICAgIHByaW50KGYie2t9IHdyaXR0ZW4gdG8gR0lUSFVCX0VOViIpCgoKaWYgX19uYW1lX18gPT0gIl9fbWFpbl9fIjoKICAgIHNldF9lbnYoc3lzLmFyZ3ZbMV0pCg== - INPUTS_ENV: ${{ inputs.env }} - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - fetch-depth: 0 - lfs: true - submodules: ${{ inputs.submodules }} - ref: ${{ inputs.checkout_ref }} - persist-credentials: false - - name: Install dependencies - if: ${{ inputs.libraries != '' }} - uses: ConorMacBride/install-package@3e7ad059e07782ee54fa35f827df52aae0626f30 # v1.1.0 - with: - apt: ${{ inputs.libraries }} - - id: build - uses: OpenAstronomy/build-python-dist@541e0aa2abd94d679c4939189fa205597122c948 # v1.2.0 - with: - test_groups: ${{ inputs.test_groups }} - test_extras: ${{ inputs.test_extras }} - test_command: ${{ inputs.test_command }} - python-version: ${{ inputs.python-version }} - pure_python_wheel: true - source-directory: ${{ inputs.working-directory }} - - id: set-upload - run: | - if [ $UPLOAD_TO_PYPI == "true" ] || [ $UPLOAD_TAG == "true" ]; - then - echo "upload_to_pypi=true" >> $GITHUB_OUTPUT - else - echo "upload_to_pypi=false" >> $GITHUB_OUTPUT - fi - env: - UPLOAD_TO_PYPI: ${{ inputs.upload_to_pypi }} - UPLOAD_TAG: ${{ startsWith(inputs.upload_to_pypi, 'refs/tags/') && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || github.event_name == 'release' || github.event_name == 'create') && startsWith(github.ref, inputs.upload_to_pypi) }} - - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 - if: ${{ inputs.save_artifacts }} - with: - name: "dist-publish-pure" - path: dist/* - - uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0 - name: Upload to PyPI - if: ${{ steps.set-upload.outputs.upload_to_pypi == 'true' }} - with: - user: __token__ - password: ${{ secrets.pypi_token }} # zizmor: ignore[secrets-outside-env] - repository-url: ${{ inputs.repository_url }} - attestations: false - - uses: OpenAstronomy/publish-wheels-anaconda@eae491141709933a7636af70b070e4a92d1a45be # v1.0.1 - if: ${{ inputs.upload_to_anaconda }} - with: - anaconda_user: ${{ inputs.anaconda_user }} - anaconda_package: ${{ inputs.anaconda_package }} - anaconda_token: ${{ secrets.anaconda_token }} # zizmor: ignore[secrets-outside-env] - keep_n_latest: ${{ inputs.anaconda_keep_n_latest }} +name: Build and publish pure Python package + +on: + workflow_call: + inputs: + test_groups: + description: PEP 735 dependency groups that should be installed for testing + required: false + default: '' + type: string + test_extras: + description: Any extras_requires modifier that should be used to install the package for testing + required: false + default: '' + type: string + test_command: + description: The command to run to test the package (will be run in a temporary directory) + required: false + default: '' + type: string + env: + description: A map of environment variables to be available when building and testing + required: false + default: '' + type: string + libraries: + description: Packages needed to build the source distribution for testing (installed using apt) + required: false + default: '' + type: string + runs-on: + description: Which runner image to use to build and test the sdist and wheel + required: false + default: 'ubuntu-latest' + type: string + save_artifacts: + description: Upload the built dist(s) as github artifacts + required: false + default: false + type: boolean + upload_to_pypi: + description: A condition specifying whether to upload to PyPI + required: false + default: 'refs/tags/v' + type: string + repository_url: + description: The PyPI repository URL to use + required: false + default: '' + type: string + upload_to_anaconda: + description: A condition specifying whether to upload to Anaconda.org + required: false + default: false + type: boolean + anaconda_user: + description: Anaconda.org user or organisation + required: false + default: '' + type: string + anaconda_package: + description: Anaconda.org package name + required: false + default: '' + type: string + anaconda_keep_n_latest: + description: If specified, only this number of the most recent versions are kept + required: false + default: -1 + type: number + timeout-minutes: + description: The maximum number of minutes to let the workflow run before GitHub automatically cancels it + required: false + default: 360 + type: number + submodules: + description: Whether to checkout submodules + required: false + default: true + type: boolean + python-version: + description: The Python version to use for building and testing + required: false + default: '3.x' + type: string + checkout_ref: + description: The ref to checkout + required: false + default: '' + type: string + working-directory: + description: The working directory containing the package to build, relative to the repository root + required: false + default: '.' + type: string + secrets: + pypi_token: + required: false + anaconda_token: + required: false + +jobs: + + build: + name: Build source and wheel distribution + runs-on: ${{ inputs.runs-on }} + timeout-minutes: ${{ inputs.timeout-minutes }} + steps: + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + if: ${{ inputs.env != '' }} + with: + python-version: '3.12' + - id: set-env + if: ${{ inputs.env != '' }} + run: | + echo $SET_ENV_SCRIPT | base64 --decode > set_env.py + pipx run set_env.py "${INPUTS_ENV}" + rm set_env.py + shell: sh + env: + SET_ENV_SCRIPT: IyAvLy8gc2NyaXB0CiMgcmVxdWlyZXMtcHl0aG9uID0gIj09My4xMiIKIyBkZXBlbmRlbmNpZXMgPSBbCiMgICAgICJweXlhbWw9PTYuMC4yIiwKIyBdCiMgLy8vCmltcG9ydCBqc29uCmltcG9ydCBvcwppbXBvcnQgc3lzCgppbXBvcnQgeWFtbAoKR0lUSFVCX0VOViA9IG9zLmdldGVudigiR0lUSFVCX0VOViIpCmlmIEdJVEhVQl9FTlYgaXMgTm9uZToKICAgIHJhaXNlIFZhbHVlRXJyb3IoIkdJVEhVQl9FTlYgbm90IHNldC4gTXVzdCBiZSBydW4gaW5zaWRlIEdpdEh1YiBBY3Rpb25zLiIpCgpERUxJTUlURVIgPSAiRU9GIgoKCmRlZiBzZXRfZW52KGVudik6CgogICAgZW52ID0geWFtbC5sb2FkKGVudiwgTG9hZGVyPXlhbWwuQmFzZUxvYWRlcikKICAgIHByaW50KGpzb24uZHVtcHMoZW52LCBpbmRlbnQ9MikpCgogICAgaWYgbm90IGlzaW5zdGFuY2UoZW52LCBkaWN0KToKICAgICAgICB0aXRsZSA9ICJgZW52YCBtdXN0IGJlIG1hcHBpbmciCiAgICAgICAgbWVzc2FnZSA9IGYiYGVudmAgbXVzdCBiZSBtYXBwaW5nIG9mIGVudiB2YXJpYWJsZXMgdG8gdmFsdWVzLCBnb3QgdHlwZSB7dHlwZShlbnYpfSIKICAgICAgICBwcmludChmIjo6ZXJyb3IgdGl0bGU9e3RpdGxlfTo6e21lc3NhZ2V9IikKICAgICAgICBleGl0KDEpCgogICAgZm9yIGssIHYgaW4gZW52Lml0ZW1zKCk6CgogICAgICAgIGlmIG5vdCBpc2luc3RhbmNlKHYsIHN0cik6CiAgICAgICAgICAgIHRpdGxlID0gImBlbnZgIHZhbHVlcyBtdXN0IGJlIHN0cmluZ3MiCiAgICAgICAgICAgIG1lc3NhZ2UgPSBmImBlbnZgIHZhbHVlcyBtdXN0IGJlIHN0cmluZ3MsIGJ1dCB2YWx1ZSBvZiB7a30gaGFzIHR5cGUge3R5cGUodil9IgogICAgICAgICAgICBwcmludChmIjo6ZXJyb3IgdGl0bGU9e3RpdGxlfTo6e21lc3NhZ2V9IikKICAgICAgICAgICAgZXhpdCgxKQoKICAgICAgICB2ID0gdi5zcGxpdCgiXG4iKQoKICAgICAgICB3aXRoIG9wZW4oR0lUSFVCX0VOViwgImEiKSBhcyBmOgogICAgICAgICAgICBpZiBsZW4odikgPT0gMToKICAgICAgICAgICAgICAgIGYud3JpdGUoZiJ7a309e3ZbMF19XG4iKQogICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgZm9yIGxpbmUgaW4gdjoKICAgICAgICAgICAgICAgICAgICBhc3NlcnQgbGluZS5zdHJpcCgpICE9IERFTElNSVRFUgogICAgICAgICAgICAgICAgZi53cml0ZShmIntrfTw8e0RFTElNSVRFUn1cbiIpCiAgICAgICAgICAgICAgICBmb3IgbGluZSBpbiB2OgogICAgICAgICAgICAgICAgICAgIGYud3JpdGUoZiJ7bGluZX1cbiIpCiAgICAgICAgICAgICAgICBmLndyaXRlKGYie0RFTElNSVRFUn1cbiIpCgogICAgICAgIHByaW50KGYie2t9IHdyaXR0ZW4gdG8gR0lUSFVCX0VOViIpCgoKaWYgX19uYW1lX18gPT0gIl9fbWFpbl9fIjoKICAgIHNldF9lbnYoc3lzLmFyZ3ZbMV0pCg== + INPUTS_ENV: ${{ inputs.env }} + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + lfs: true + submodules: ${{ inputs.submodules }} + ref: ${{ inputs.checkout_ref }} + persist-credentials: false + - name: Install dependencies + if: ${{ inputs.libraries != '' }} + uses: ConorMacBride/install-package@3e7ad059e07782ee54fa35f827df52aae0626f30 # v1.1.0 + with: + apt: ${{ inputs.libraries }} + - id: build + uses: OpenAstronomy/build-python-dist@541e0aa2abd94d679c4939189fa205597122c948 # v1.2.0 + with: + test_groups: ${{ inputs.test_groups }} + test_extras: ${{ inputs.test_extras }} + test_command: ${{ inputs.test_command }} + python-version: ${{ inputs.python-version }} + pure_python_wheel: true + source-directory: ${{ inputs.working-directory }} + - id: set-upload + run: | + if [ $UPLOAD_TO_PYPI == "true" ] || [ $UPLOAD_TAG == "true" ]; + then + echo "upload_to_pypi=true" >> $GITHUB_OUTPUT + else + echo "upload_to_pypi=false" >> $GITHUB_OUTPUT + fi + env: + UPLOAD_TO_PYPI: ${{ inputs.upload_to_pypi }} + UPLOAD_TAG: ${{ startsWith(inputs.upload_to_pypi, 'refs/tags/') && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || github.event_name == 'release' || github.event_name == 'create') && startsWith(github.ref, inputs.upload_to_pypi) }} + - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + if: ${{ inputs.save_artifacts }} + with: + name: "dist-publish-pure" + path: dist/* + - uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0 + name: Upload to PyPI + if: ${{ steps.set-upload.outputs.upload_to_pypi == 'true' }} + with: + user: __token__ + password: ${{ secrets.pypi_token }} # zizmor: ignore[secrets-outside-env] + repository-url: ${{ inputs.repository_url }} + attestations: false + - uses: OpenAstronomy/publish-wheels-anaconda@eae491141709933a7636af70b070e4a92d1a45be # v1.0.1 + if: ${{ inputs.upload_to_anaconda }} + with: + anaconda_user: ${{ inputs.anaconda_user }} + anaconda_package: ${{ inputs.anaconda_package }} + anaconda_token: ${{ secrets.anaconda_token }} # zizmor: ignore[secrets-outside-env] + keep_n_latest: ${{ inputs.anaconda_keep_n_latest }} diff --git a/.github/workflows/tox.yml b/.github/workflows/tox.yml index d8860ba..4f1d975 100644 --- a/.github/workflows/tox.yml +++ b/.github/workflows/tox.yml @@ -1,352 +1,352 @@ -name: Test Python package - -on: - workflow_call: - inputs: - envs: - description: Array of tox environments to test - required: true - type: string - fill: - description: Add an extra toxenv to the matrix for each current version of Python supported by the package - required: false - default: false - type: boolean - fill_platforms: - description: Platforms to iterate with fill - required: false - default: '' - type: string - fill_factors: - description: Tox factors to add to toxenvs added with `fill` - required: false - default: '' - type: string - libraries: - description: Additional packages to install - required: false - default: '' - type: string - posargs: - description: Positional arguments for the underlying tox test command - required: false - default: '' - type: string - toxdeps: - description: additional packages to install alongside tox (i.e. `tox-uv`) - required: false - default: '' - type: string - toxargs: - description: Positional arguments for tox - required: false - default: '' - type: string - pytest: - description: Whether pytest is run - required: false - default: true - type: boolean - pytest-results-summary: - description: Whether to report test summary - required: false - default: false - type: boolean - coverage: - description: Coverage providers to upload to - required: false - default: '' - type: string - conda: - description: Whether to test with conda (deprecated) - required: false - default: 'auto' - type: string - setenv: - description: A map of environment variables to be available when testing - required: false - default: '' - type: string - display: - description: Whether to setup a headless display - required: false - default: false - type: boolean - cache-path: - description: A list of files, directories, and wildcard patterns to cache and restore - required: false - default: '' - type: string - cache-key: - description: An explicit key for restoring and saving the cache - required: false - default: '' - type: string - cache-restore-keys: - description: An ordered list of keys to use for restoring the cache if no cache hit occurred for key - required: false - default: '' - type: string - artifact-path: - description: A list of files, directories, and wildcard patterns to upload as artifacts - required: false - default: '' - type: string - artifact-archive: - description: A flag to determine if the artifact is zipped, if false globs are not supported in archive-path. - required: false - default: true - type: boolean - artifact-include-hidden-files: - description: A flag to determine if the artifact includes hidden files. - required: false - default: false - type: boolean - artifact-if-no-files-found: - description: A flag to determine behavior if no matching files found. - required: false - default: 'warn' - type: string - runs-on: - description: Which runner image to use for each OS - required: false - default: '' - type: string - default_python: - description: Default version of Python - required: false - default: '3' - type: string - fail-fast: - description: Whether to cancel all in-progress jobs if any job fails - required: false - default: false - type: boolean - timeout-minutes: - description: The maximum number of minutes to let a job run before GitHub automatically cancels it - required: false - default: 360 - type: number - submodules: - description: Whether to checkout submodules - required: false - default: true - type: boolean - checkout_ref: - description: The ref to checkout - required: false - default: '' - type: string - working-directory: - description: The working directory for running tox, relative to the repository root - required: false - default: '.' - type: string - secrets: - CODECOV_TOKEN: - description: Codecov upload token - required: false - -jobs: - - envs: - name: Load tox environments - runs-on: ubuntu-latest - steps: - - uses: astral-sh/setup-uv@eac588ad8def6316056a12d4907a9d4d84ff7a3b # 7.3.0 - with: - version: "0.10.6" - enable-cache: false - ignore-empty-workdir: "true" - - if: inputs.fill - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - with: - ref: ${{ inputs.checkout_ref }} - persist-credentials: false - - if: inputs.fill - run: echo $SUPPORTED_PYTHONS_SCRIPT | base64 --decode > supported_pythons.py - env: - SUPPORTED_PYTHONS_SCRIPT: IyAvLy8gc2NyaXB0CiMgcmVxdWlyZXMtcHl0aG9uID0gIj49My4xMiIKIyBkZXBlbmRlbmNpZXMgPSBbCiMgICAgICJjbGljaz09OC4yLjEiLAojICAgICAicGFja2FnaW5nPT0yNS4wIiwKIyAgICAgInJlcXVlc3RzPT0yLjMyLjUiLAojICAgICAidG9tbGk9PTIuNC4wIiwKIyBdCiMgLy8vCmltcG9ydCBvcwppbXBvcnQgd2FybmluZ3MKZnJvbSBwYXRobGliIGltcG9ydCBQYXRoCgppbXBvcnQgY2xpY2sKaW1wb3J0IHJlcXVlc3RzCmltcG9ydCB0b21saQpmcm9tIHBhY2thZ2luZy5zcGVjaWZpZXJzIGltcG9ydCBTcGVjaWZpZXJTZXQKZnJvbSBwYWNrYWdpbmcudmVyc2lvbiBpbXBvcnQgVmVyc2lvbgoKCkBjbGljay5jb21tYW5kKCkKQGNsaWNrLm9wdGlvbigiLS1wYWNrYWdlLXNvdXJjZSIsIGRlZmF1bHQ9Tm9uZSkKQGNsaWNrLm9wdGlvbigiLS1mYWN0b3JzIiwgZGVmYXVsdD1Ob25lKQpAY2xpY2sub3B0aW9uKCItLW5vLWVvYXMiLCBpc19mbGFnPVRydWUsIGRlZmF1bHQ9RmFsc2UpCkBjbGljay5vcHRpb24oIi0tcGxhdGZvcm1zIiwgZGVmYXVsdD1Ob25lKQpkZWYgc3VwcG9ydGVkX3B5dGhvbl9lbnZzX2Jsb2NrKAogICAgcGFja2FnZV9zb3VyY2U6IFBhdGggPSBOb25lLAogICAgZmFjdG9yczogbGlzdFtzdHJdID0gTm9uZSwKICAgIG5vX2VvYXM6IGJvb2wgPSBGYWxzZSwKICAgIHBsYXRmb3JtczogbGlzdFtzdHJdID0gTm9uZSwKKToKICAgICIiImVudW1lcmF0ZSB0b3hlbnZzIGZvciBlYWNoIFB5dGhvbiB2ZXJzaW9uIHN1cHBvcnRlZCBieSBwYWNrYWdlIiIiCgogICAgaWYgcGxhdGZvcm1zIGlzIE5vbmU6CiAgICAgICAgcGxhdGZvcm1zID0gWyJsaW51eCJdCiAgICBlbGlmIGlzaW5zdGFuY2UocGxhdGZvcm1zLCBzdHIpOgogICAgICAgIHBsYXRmb3JtcyA9IHBsYXRmb3Jtcy5zcGxpdCgiLCIpCgogICAgdG94ZW52cyA9IHN1cHBvcnRlZF9weXRob25fdG94ZW52cyhwYWNrYWdlX3NvdXJjZSwgZmFjdG9ycywgbm9fZW9hcykKICAgIGVudnNfYmxvY2sgPSAiXFxuIi5qb2luKAogICAgICAgIGYiLSB7cGxhdGZvcm19OiB7dG94ZW52fSIgZm9yIHBsYXRmb3JtIGluIHBsYXRmb3JtcyBmb3IgdG94ZW52IGluIHRveGVudnMKICAgICkKCiAgICBwcmludChlbnZzX2Jsb2NrKQogICAgd2l0aCBvcGVuKG9zLmVudmlyb25bIkdJVEhVQl9PVVRQVVQiXSwgImEiKSBhcyBmOgogICAgICAgIGYud3JpdGUoZiJlbnZzPXtlbnZzX2Jsb2NrfVxuIikKCgpkZWYgc3VwcG9ydGVkX3B5dGhvbl90b3hlbnZzKAogICAgcGFja2FnZV9zb3VyY2U6IFBhdGggPSBOb25lLAogICAgZmFjdG9yczogbGlzdFtzdHJdID0gTm9uZSwKICAgIG5vX2VvYXM6IGJvb2wgPSBGYWxzZSwKKSAtPiBsaXN0W3N0cl06CiAgICBpZiBpc2luc3RhbmNlKGZhY3RvcnMsIHN0cik6CiAgICAgICAgZmFjdG9ycyA9IGZhY3RvcnMuc3BsaXQoIiwiKQoKICAgIHJldHVybiBbCiAgICAgICAgZiJweXtzdHIocHl0aG9uX3ZlcnNpb24pLnJlcGxhY2UoJy4nLCAnJyl9eyctJyArICctJy5qb2luKGZhY3RvcnMpIGlmIGZhY3RvcnMgaXMgbm90IE5vbmUgYW5kIGxlbihmYWN0b3JzKSA+IDAgZWxzZSAnJ30iCiAgICAgICAgZm9yIHB5dGhvbl92ZXJzaW9uIGluIHN1cHBvcnRlZF9weXRob25zKHBhY2thZ2Vfc291cmNlLCBub19lb2FzPW5vX2VvYXMpCiAgICBdCgoKZGVmIHN1cHBvcnRlZF9weXRob25zKAogICAgcGFja2FnZV9zb3VyY2U6IFBhdGggPSBOb25lLAogICAgbm9fZW9hczogYm9vbCA9IEZhbHNlLAopIC0+IGxpc3RbVmVyc2lvbl06CiAgICBjdXJyZW50X3B5dGhvbl92ZXJzaW9ucyA9IGN1cnJlbnRfcHl0aG9ucyhub19lb2FzPW5vX2VvYXMpCgogICAgaWYgbm90IHBhY2thZ2Vfc291cmNlOgogICAgICAgIHN1cHBvcnRlZF92ZXJzaW9ucyA9IGN1cnJlbnRfcHl0aG9uX3ZlcnNpb25zCiAgICBlbHNlOgogICAgICAgIHRyeToKICAgICAgICAgICAgcHlwcm9qZWN0X3RvbWxfZmlsZW5hbWUgPSBQYXRoKHBhY2thZ2Vfc291cmNlKSAvICJweXByb2plY3QudG9tbCIKICAgICAgICAgICAgaWYgcHlwcm9qZWN0X3RvbWxfZmlsZW5hbWUuZXhpc3RzKCk6CiAgICAgICAgICAgICAgICB3aXRoIG9wZW4ocHlwcm9qZWN0X3RvbWxfZmlsZW5hbWUsICJyYiIpIGFzIHB5cHJvamVjdF90b21sX2ZpbGU6CiAgICAgICAgICAgICAgICAgICAgcHlwcm9qZWN0X3RvbWwgPSB0b21saS5sb2FkKHB5cHJvamVjdF90b21sX2ZpbGUpCiAgICAgICAgICAgICAgICBpZiAicHJvamVjdCIgaW4gcHlwcm9qZWN0X3RvbWw6CiAgICAgICAgICAgICAgICAgICAgcHJvamVjdF9tZXRhZGF0YSA9IHB5cHJvamVjdF90b21sWyJ0YWJsZSJdCiAgICAgICAgICAgICAgICAgICAgaWYgInJlcXVpcmVzX3B5dGhvbiIgaW4gcHJvamVjdF9tZXRhZGF0YToKICAgICAgICAgICAgICAgICAgICAgICAgcHl0aG9uX3ZlcnNpb25fcmVxdWlyZW1lbnRzID0gU3BlY2lmaWVyU2V0KAogICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJvamVjdF9tZXRhZGF0YVsicmVxdWlyZXMtcHl0aG9uIl0KICAgICAgICAgICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICAgICAgICAgIHJhaXNlIEtleUVycm9yKAogICAgICAgICAgICAgICAgICAgICAgICAgICAgImBwcm9qZWN0LnJlcXVpcmVzX3B5dGhvbmAgbm90IGZvdW5kIGluIGBweXByb2plY3QudG9tbGA7IGVuc3VyZSB5b3VyIHBhY2thZ2UgY29uZm9ybXMgdG8gUEVQNjIxIgogICAgICAgICAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgICAgIHJhaXNlIEtleUVycm9yKAogICAgICAgICAgICAgICAgICAgICAgICAiYHByb2plY3RgIG5vdCBmb3VuZCBpbiBgcHlwcm9qZWN0LnRvbWxgOyBlbnN1cmUgeW91ciBwYWNrYWdlIGNvbmZvcm1zIHRvIFBFUDYyMSIKICAgICAgICAgICAgICAgICAgICApCiAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICByYWlzZSBGaWxlTm90Rm91bmRFcnJvcigKICAgICAgICAgICAgICAgICAgICAiY291bGQgbm90IGZpbmQgYHB5cHJvamVjdC50b21sYCBpbiB0aGUgcHJvdmlkZWQgcGFja2FnZSBzb3VyY2U7IGVuc3VyZSB5b3VyIHBhY2thZ2UgY29uZm9ybXMgdG8gUEVQNjIxIgogICAgICAgICAgICAgICAgKQoKICAgICAgICAgICAgc3VwcG9ydGVkX3ZlcnNpb25zID0gWwogICAgICAgICAgICAgICAgcHl0aG9uX3ZlcnNpb24KICAgICAgICAgICAgICAgIGZvciBweXRob25fdmVyc2lvbiBpbiBjdXJyZW50X3B5dGhvbl92ZXJzaW9ucwogICAgICAgICAgICAgICAgaWYgcHl0aG9uX3ZlcnNpb24gaW4gcHl0aG9uX3ZlcnNpb25fcmVxdWlyZW1lbnRzCiAgICAgICAgICAgIF0KICAgICAgICBleGNlcHQgKEtleUVycm9yLCBUeXBlRXJyb3IsIEZpbGVOb3RGb3VuZEVycm9yKSBhcyBlcnJvcjoKICAgICAgICAgICAgd2FybmluZ3Mud2FybihzdHIoZXJyb3IpKQogICAgICAgICAgICB3YXJuaW5ncy53YXJuKCJmYWxsaW5nIGJhY2sgdG8gY3VycmVudCBQeXRob24gdmVyc2lvbnMuLi4iKQogICAgICAgICAgICBzdXBwb3J0ZWRfdmVyc2lvbnMgPSBjdXJyZW50X3B5dGhvbl92ZXJzaW9ucwoKICAgIHJldHVybiBzdXBwb3J0ZWRfdmVyc2lvbnMKCgpkZWYgY3VycmVudF9weXRob25zKG5vX2VvYXM6IGJvb2wgPSBGYWxzZSkgLT4gbGlzdFtWZXJzaW9uXToKICAgIHVybCA9ICJodHRwczovL2VuZG9mbGlmZS5kYXRlL2FwaS92MS9wcm9kdWN0cy9weXRob24iCiAgICByZXNwb25zZSA9IHJlcXVlc3RzLmdldCgiaHR0cHM6Ly9lbmRvZmxpZmUuZGF0ZS9hcGkvdjEvcHJvZHVjdHMvcHl0aG9uIikKICAgIGlmIHJlc3BvbnNlLnN0YXR1c19jb2RlID09IDIwMDoKICAgICAgICByZXR1cm4gWwogICAgICAgICAgICBWZXJzaW9uKHB5dGhvbl92ZXJzaW9uWyJuYW1lIl0pCiAgICAgICAgICAgIGZvciBweXRob25fdmVyc2lvbiBpbiByZXNwb25zZS5qc29uKClbInJlc3VsdCJdWyJyZWxlYXNlcyJdCiAgICAgICAgICAgIGlmIG5vdCBweXRob25fdmVyc2lvblsiaXNFb2FzIiBpZiBub19lb2FzIGVsc2UgImlzRW9sIl0KICAgICAgICBdCiAgICBlbHNlOgogICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoZiJyZXF1ZXN0IHRvIHt1cmx9IHJldHVybmVkIHN0YXR1cyBjb2RlIHtyZXNwb25zZS5zdGF0dXNfY29kZX0iKQoKCmlmIF9fbmFtZV9fID09ICJfX21haW5fXyI6CiAgICBzdXBwb3J0ZWRfcHl0aG9uX2VudnNfYmxvY2soKQo= - - if: inputs.fill # zizmor: ignore[template-injection] - id: supported-pythons - run: uv run supported_pythons.py --package-source . ${{ inputs.fill_platforms != '' && format('--platforms {0}', inputs.fill_platforms) || '' }} ${{ inputs.fill_factors != '' && format('--factors {0}', inputs.fill_factors) || '' }} - shell: sh - - run: echo $TOX_MATRIX_SCRIPT | base64 --decode > tox_matrix.py - env: - TOX_MATRIX_SCRIPT: IyAvLy8gc2NyaXB0CiMgcmVxdWlyZXMtcHl0aG9uID0gIj09My4xMiIKIyBkZXBlbmRlbmNpZXMgPSBbCiMgICAgICJjbGljaz09OC4yLjEiLAojICAgICAicHl5YW1sPT02LjAuMiIsCiMgXQojIC8vLwppbXBvcnQganNvbgppbXBvcnQgb3MKaW1wb3J0IHJlCmltcG9ydCB3YXJuaW5ncwoKaW1wb3J0IGNsaWNrCmltcG9ydCB5YW1sCgoKQGNsaWNrLmNvbW1hbmQoKQpAY2xpY2sub3B0aW9uKCItLWVudnMiLCBkZWZhdWx0PSIiKQpAY2xpY2sub3B0aW9uKCItLWxpYnJhcmllcyIsIGRlZmF1bHQ9IiIpCkBjbGljay5vcHRpb24oIi0tcG9zYXJncyIsIGRlZmF1bHQ9IiIpCkBjbGljay5vcHRpb24oIi0tdG94ZGVwcyIsIGRlZmF1bHQ9IiIpCkBjbGljay5vcHRpb24oIi0tdG94YXJncyIsIGRlZmF1bHQ9IiIpCkBjbGljay5vcHRpb24oIi0tcHl0ZXN0IiwgZGVmYXVsdD0idHJ1ZSIpCkBjbGljay5vcHRpb24oIi0tcHl0ZXN0LXJlc3VsdHMtc3VtbWFyeSIsIGRlZmF1bHQ9ImZhbHNlIikKQGNsaWNrLm9wdGlvbigiLS1jb3ZlcmFnZSIsIGRlZmF1bHQ9IiIpCkBjbGljay5vcHRpb24oIi0tY29uZGEiLCBkZWZhdWx0PSJhdXRvIikKQGNsaWNrLm9wdGlvbigiLS1zZXRlbnYiLCBkZWZhdWx0PSIiKQpAY2xpY2sub3B0aW9uKCItLWRpc3BsYXkiLCBkZWZhdWx0PSJmYWxzZSIpCkBjbGljay5vcHRpb24oIi0tY2FjaGUtcGF0aCIsIGRlZmF1bHQ9IiIpCkBjbGljay5vcHRpb24oIi0tY2FjaGUta2V5IiwgZGVmYXVsdD0iIikKQGNsaWNrLm9wdGlvbigiLS1jYWNoZS1yZXN0b3JlLWtleXMiLCBkZWZhdWx0PSIiKQpAY2xpY2sub3B0aW9uKCItLWFydGlmYWN0LXBhdGgiLCBkZWZhdWx0PSIiKQpAY2xpY2sub3B0aW9uKCItLWFydGlmYWN0LWFyY2hpdmUiLCBkZWZhdWx0PSJ0cnVlIikKQGNsaWNrLm9wdGlvbigiLS1hcnRpZmFjdC1pbmNsdWRlLWhpZGRlbi1maWxlcyIsIGRlZmF1bHQ9ImZhbHNlIikKQGNsaWNrLm9wdGlvbigiLS1hcnRpZmFjdC1pZi1uby1maWxlcy1mb3VuZCIsIGRlZmF1bHQ9Indhcm4iKQpAY2xpY2sub3B0aW9uKCItLXJ1bnMtb24iLCBkZWZhdWx0PSIiKQpAY2xpY2sub3B0aW9uKCItLWRlZmF1bHQtcHl0aG9uIiwgZGVmYXVsdD0iIikKQGNsaWNrLm9wdGlvbigiLS10aW1lb3V0LW1pbnV0ZXMiLCBkZWZhdWx0PSIzNjAiKQpkZWYgbG9hZF90b3hfdGFyZ2V0cygKICAgIGVudnMsCiAgICBsaWJyYXJpZXMsCiAgICBwb3NhcmdzLAogICAgdG94ZGVwcywKICAgIHRveGFyZ3MsCiAgICBweXRlc3QsCiAgICBweXRlc3RfcmVzdWx0c19zdW1tYXJ5LAogICAgY292ZXJhZ2UsCiAgICBjb25kYSwKICAgIHNldGVudiwKICAgIGRpc3BsYXksCiAgICBjYWNoZV9wYXRoLAogICAgY2FjaGVfa2V5LAogICAgY2FjaGVfcmVzdG9yZV9rZXlzLAogICAgYXJ0aWZhY3RfcGF0aCwKICAgIGFydGlmYWN0X2FyY2hpdmUsCiAgICBhcnRpZmFjdF9pbmNsdWRlX2hpZGRlbl9maWxlcywKICAgIGFydGlmYWN0X2lmX25vX2ZpbGVzX2ZvdW5kLAogICAgcnVuc19vbiwKICAgIGRlZmF1bHRfcHl0aG9uLAogICAgdGltZW91dF9taW51dGVzLAopOgogICAgIiIiU2NyaXB0IHRvIGxvYWQgdG94IHRhcmdldHMgZm9yIEdpdEh1YiBBY3Rpb25zIHdvcmtmbG93LiIiIgogICAgIyBMb2FkIGVudnMgY29uZmlnCiAgICBlbnZzID0geWFtbC5sb2FkKGVudnMucmVwbGFjZSgiXFxuIiwgIlxuIiksIExvYWRlcj15YW1sLkJhc2VMb2FkZXIpCiAgICBwcmludChqc29uLmR1bXBzKGVudnMsIGluZGVudD0yKSkKCiAgICAjIExvYWQgZ2xvYmFsIGxpYnJhcmllcyBjb25maWcKICAgIGdsb2JhbF9saWJyYXJpZXMgPSB7CiAgICAgICAgImJyZXciOiBbXSwKICAgICAgICAiYnJldy1jYXNrIjogW10sCiAgICAgICAgImFwdCI6IFtdLAogICAgICAgICJjaG9jbyI6IFtdLAogICAgfQogICAgbGlicmFyaWVzID0geWFtbC5sb2FkKGxpYnJhcmllcywgTG9hZGVyPXlhbWwuQmFzZUxvYWRlcikKICAgIGlmIGxpYnJhcmllcyBpcyBub3QgTm9uZToKICAgICAgICBnbG9iYWxfbGlicmFyaWVzLnVwZGF0ZShsaWJyYXJpZXMpCiAgICBwcmludChqc29uLmR1bXBzKGdsb2JhbF9saWJyYXJpZXMsIGluZGVudD0yKSkKCiAgICAjIERlZmF1bHQgaW1hZ2VzIHRvIHVzZSBmb3IgcnVubmVycwogICAgZGVmYXVsdF9ydW5zX29uID0gewogICAgICAgICJsaW51eCI6ICJ1YnVudHUtbGF0ZXN0IiwKICAgICAgICAibWFjb3MiOiAibWFjb3MtbGF0ZXN0IiwKICAgICAgICAid2luZG93cyI6ICJ3aW5kb3dzLWxhdGVzdCIsCiAgICB9CiAgICBjdXN0b21fcnVuc19vbiA9IHlhbWwubG9hZChydW5zX29uLCBMb2FkZXI9eWFtbC5CYXNlTG9hZGVyKQogICAgaWYgaXNpbnN0YW5jZShjdXN0b21fcnVuc19vbiwgZGljdCk6CiAgICAgICAgZGVmYXVsdF9ydW5zX29uLnVwZGF0ZShjdXN0b21fcnVuc19vbikKICAgIHByaW50KGpzb24uZHVtcHMoZGVmYXVsdF9ydW5zX29uLCBpbmRlbnQ9MikpCgogICAgIyBEZWZhdWx0IHN0cmluZyBwYXJhbWV0ZXJzIHdoaWNoIGNhbiBiZSBvdmVyd3JpdHRlbiBieSBlYWNoIGVudgogICAgc3RyaW5nX3BhcmFtZXRlcnMgPSB7CiAgICAgICAgInBvc2FyZ3MiOiBwb3NhcmdzLAogICAgICAgICJ0b3hkZXBzIjogdG94ZGVwcywKICAgICAgICAidG94YXJncyI6IHRveGFyZ3MsCiAgICAgICAgInB5dGVzdCI6IHB5dGVzdCwKICAgICAgICAicHl0ZXN0LXJlc3VsdHMtc3VtbWFyeSI6IHB5dGVzdF9yZXN1bHRzX3N1bW1hcnksCiAgICAgICAgImNvdmVyYWdlIjogY292ZXJhZ2UsCiAgICAgICAgImNvbmRhIjogY29uZGEsCiAgICAgICAgInNldGVudiI6IHNldGVudiwKICAgICAgICAiZGlzcGxheSI6IGRpc3BsYXksCiAgICAgICAgImNhY2hlLXBhdGgiOiBjYWNoZV9wYXRoLAogICAgICAgICJjYWNoZS1rZXkiOiBjYWNoZV9rZXksCiAgICAgICAgImNhY2hlLXJlc3RvcmUta2V5cyI6IGNhY2hlX3Jlc3RvcmVfa2V5cywKICAgICAgICAiYXJ0aWZhY3QtcGF0aCI6IGFydGlmYWN0X3BhdGgsCiAgICAgICAgImFydGlmYWN0LWFyY2hpdmUiOiBhcnRpZmFjdF9hcmNoaXZlLAogICAgICAgICJhcnRpZmFjdC1pbmNsdWRlLWhpZGRlbi1maWxlcyI6IGFydGlmYWN0X2luY2x1ZGVfaGlkZGVuX2ZpbGVzLAogICAgICAgICJhcnRpZmFjdC1pZi1uby1maWxlcy1mb3VuZCI6IGFydGlmYWN0X2lmX25vX2ZpbGVzX2ZvdW5kLAogICAgICAgICJ0aW1lb3V0LW1pbnV0ZXMiOiB0aW1lb3V0X21pbnV0ZXMsCiAgICB9CgogICAgIyBDcmVhdGUgbWF0cml4CiAgICBtYXRyaXggPSB7ImluY2x1ZGUiOiBbXX0KICAgIGZvciBlbnYgaW4gZW52czoKICAgICAgICBtYXRyaXhbImluY2x1ZGUiXS5hcHBlbmQoCiAgICAgICAgICAgIGdldF9tYXRyaXhfaXRlbSgKICAgICAgICAgICAgICAgIGVudiwKICAgICAgICAgICAgICAgIGdsb2JhbF9saWJyYXJpZXM9Z2xvYmFsX2xpYnJhcmllcywKICAgICAgICAgICAgICAgIGdsb2JhbF9zdHJpbmdfcGFyYW1ldGVycz1zdHJpbmdfcGFyYW1ldGVycywKICAgICAgICAgICAgICAgIHJ1bnNfb249ZGVmYXVsdF9ydW5zX29uLAogICAgICAgICAgICAgICAgZGVmYXVsdF9weXRob249ZGVmYXVsdF9weXRob24sCiAgICAgICAgICAgICkKICAgICAgICApCgogICAgIyBPdXRwdXQgbWF0cml4CiAgICBwcmludChqc29uLmR1bXBzKG1hdHJpeCwgaW5kZW50PTIpKQogICAgd2l0aCBvcGVuKG9zLmVudmlyb25bIkdJVEhVQl9PVVRQVVQiXSwgImEiKSBhcyBmOgogICAgICAgIGYud3JpdGUoZiJtYXRyaXg9e2pzb24uZHVtcHMobWF0cml4KX1cbiIpCgoKZGVmIGdldF9tYXRyaXhfaXRlbShlbnYsIGdsb2JhbF9saWJyYXJpZXMsIGdsb2JhbF9zdHJpbmdfcGFyYW1ldGVycywgcnVuc19vbiwgZGVmYXVsdF9weXRob24pOgoKICAgICMgZGVmaW5lIHNwZWMgZm9yIGVhY2ggbWF0cml4IGluY2x1ZGUgKCsgZ2xvYmFsX3N0cmluZ19wYXJhbWV0ZXJzKQogICAgaXRlbSA9IHsKICAgICAgICAib3MiOiBOb25lLAogICAgICAgICJ0b3hlbnYiOiBOb25lLAogICAgICAgICJweXRob25fdmVyc2lvbiI6IE5vbmUsCiAgICAgICAgIm5hbWUiOiBOb25lLAogICAgICAgICJweXRlc3RfZmxhZyI6IE5vbmUsCiAgICAgICAgImxpYnJhcmllc19icmV3IjogTm9uZSwKICAgICAgICAibGlicmFyaWVzX2JyZXdfY2FzayI6IE5vbmUsCiAgICAgICAgImxpYnJhcmllc19hcHQiOiBOb25lLAogICAgICAgICJsaWJyYXJpZXNfY2hvY28iOiBOb25lLAogICAgICAgICJjYWNoZS1wYXRoIjogTm9uZSwKICAgICAgICAiY2FjaGUta2V5IjogTm9uZSwKICAgICAgICAiY2FjaGUtcmVzdG9yZS1rZXlzIjogTm9uZSwKICAgICAgICAiYXJ0aWZhY3QtbmFtZSI6IE5vbmUsCiAgICAgICAgImFydGlmYWN0LXBhdGgiOiBOb25lLAogICAgICAgICJhcnRpZmFjdC1hcmNoaXZlIjogTm9uZSwKICAgICAgICAiYXJ0aWZhY3QtaW5jbHVkZS1oaWRkZW4tZmlsZXMiOiBOb25lLAogICAgICAgICJhcnRpZmFjdC1pZi1uby1maWxlcy1mb3VuZCI6IE5vbmUsCiAgICAgICAgInRpbWVvdXQtbWludXRlcyI6IE5vbmUsCiAgICB9CiAgICBmb3Igc3RyaW5nX3BhcmFtLCBkZWZhdWx0IGluIGdsb2JhbF9zdHJpbmdfcGFyYW1ldGVycy5pdGVtcygpOgogICAgICAgIGVudl92YWx1ZSA9IGVudi5nZXQoc3RyaW5nX3BhcmFtKQogICAgICAgIGl0ZW1bc3RyaW5nX3BhcmFtXSA9IGRlZmF1bHQgaWYgZW52X3ZhbHVlIGlzIE5vbmUgZWxzZSBlbnZfdmFsdWUKCiAgICAjIHNldCBvcyBhbmQgdG94ZW52CiAgICBmb3IgaywgdiBpbiBydW5zX29uLml0ZW1zKCk6CiAgICAgICAgaWYgayBpbiBlbnY6CiAgICAgICAgICAgIHBsYXRmb3JtID0gawogICAgICAgICAgICBpdGVtWyJvcyJdID0gZW52LmdldCgicnVucy1vbiIsIHYpCiAgICAgICAgICAgIGl0ZW1bInRveGVudiJdID0gZW52W2tdCiAgICBhc3NlcnQgaXRlbVsib3MiXSBpcyBub3QgTm9uZSBhbmQgaXRlbVsidG94ZW52Il0gaXMgbm90IE5vbmUKCiAgICAjIHNldCBweXRob25fdmVyc2lvbgogICAgcHl0aG9uX3ZlcnNpb24gPSBlbnYuZ2V0KCJweXRob24tdmVyc2lvbiIpCiAgICBtID0gcmUuc2VhcmNoKCJecHkoMnwzKShbMC05XSt0PykiLCBpdGVtWyJ0b3hlbnYiXSkKICAgIGlmIHB5dGhvbl92ZXJzaW9uIGlzIG5vdCBOb25lOgogICAgICAgIGl0ZW1bInB5dGhvbl92ZXJzaW9uIl0gPSBweXRob25fdmVyc2lvbgogICAgZWxpZiBtIGlzIG5vdCBOb25lOgogICAgICAgIG1ham9yLCBtaW5vciA9IG0uZ3JvdXBzKCkKICAgICAgICBpdGVtWyJweXRob25fdmVyc2lvbiJdID0gZiJ7bWFqb3J9LnttaW5vcn0iCiAgICBlbHNlOgogICAgICAgIGl0ZW1bInB5dGhvbl92ZXJzaW9uIl0gPSBlbnYuZ2V0KCJkZWZhdWx0X3B5dGhvbiIpIG9yIGRlZmF1bHRfcHl0aG9uCgogICAgIyBzZXQgbmFtZQogICAgaXRlbVsibmFtZSJdID0gZW52LmdldCgibmFtZSIpIG9yIGYie2l0ZW1bJ3RveGVudiddfSAoe2l0ZW1bJ29zJ119KSIKCiAgICAjIHNldCBhcnRpZmFjdC1uYW1lIChyZXBsYWNlIGludmFsaWQgcGF0aCBjaGFyYWN0ZXJzKQogICAgaXRlbVsiYXJ0aWZhY3QtbmFtZSJdID0gcmUuc3ViKHIiW1xcIC86PD58Kj9cIiddIiwgIi0iLCBpdGVtWyJuYW1lIl0pCiAgICBpdGVtWyJhcnRpZmFjdC1uYW1lIl0gPSByZS5zdWIociItKyIsICItIiwgaXRlbVsiYXJ0aWZhY3QtbmFtZSJdKQoKICAgICMgc2V0IHB5dGVzdF9mbGFnCiAgICBpdGVtWyJweXRlc3RfZmxhZyJdID0gIiIKICAgIHNlcCA9IHIiXFwiIGlmIHBsYXRmb3JtID09ICJ3aW5kb3dzIiBlbHNlICIvIgogICAgaWYgaXRlbVsicHl0ZXN0Il0gPT0gInRydWUiOgogICAgICAgIGlmICJjb2RlY292IiBpbiBpdGVtLmdldCgiY292ZXJhZ2UiLCAiIik6CiAgICAgICAgICAgICMgTm90ZSB0aGF0IHdlIGRvbid0IGluY2x1ZGUgLS1jb3YgaGVyZSBhcyBpZiBpdCdzIHByb3ZpZGVkIHRvIHB5dGVzdCB0d2ljZSBpdCBicmVha3MgY292IHJlcG9ydGluZy4KICAgICAgICAgICAgIyBMb3RzIG9mIHVzZXJzIG9mIHRoaXMgc3BlY2lmeSAtLWNvdiBpbiB0aGVpciB0b3guaW5pIHNvIGl0J3MgYmVlbiByZW1vdmVkIGZvciBiYWNrd2FyZHMgY29tcGF0aWJpbGl0eS4KICAgICAgICAgICAgIyBodHRwczovL2dpdGh1Yi5jb20vT3BlbkFzdHJvbm9teS9naXRodWItYWN0aW9ucy13b3JrZmxvd3MvaXNzdWVzLzM4MwogICAgICAgICAgICBpdGVtWyJweXRlc3RfZmxhZyJdICs9ICgKICAgICAgICAgICAgICAgIHJmIi0tY292LXJlcG9ydD14bWw6JHt7R0lUSFVCX1dPUktTUEFDRX19e3NlcH1jb3ZlcmFnZS54bWwgIgogICAgICAgICAgICApCgogICAgICAgIGlmIGl0ZW1bInB5dGVzdC1yZXN1bHRzLXN1bW1hcnkiXSA9PSAidHJ1ZSI6CiAgICAgICAgICAgIGl0ZW1bInB5dGVzdF9mbGFnIl0gKz0gcmYiLS1qdW5pdHhtbCAke3tHSVRIVUJfV09SS1NQQUNFfX17c2VwfXJlc3VsdHMueG1sICIKCiAgICAjIHNldCBsaWJyYXJpZXMKICAgIGVudl9saWJyYXJpZXMgPSBlbnYuZ2V0KCJsaWJyYXJpZXMiKQogICAgaWYgaXNpbnN0YW5jZShlbnZfbGlicmFyaWVzLCBzdHIpIGFuZCBsZW4oZW52X2xpYnJhcmllcy5zdHJpcCgpKSA9PSAwOgogICAgICAgIGVudl9saWJyYXJpZXMgPSB7fSAgIyBubyBsaWJyYXJpZXMgcmVxdWVzdGVkIGZvciBlbnZpcm9ubWVudAogICAgbGlicmFyaWVzID0gZ2xvYmFsX2xpYnJhcmllcyBpZiBlbnZfbGlicmFyaWVzIGlzIE5vbmUgZWxzZSBlbnZfbGlicmFyaWVzCiAgICBmb3IgbWFuYWdlciBpbiBbImJyZXciLCAiYnJld19jYXNrIiwgImFwdCIsICJjaG9jbyJdOgogICAgICAgIGl0ZW1bZiJsaWJyYXJpZXNfe21hbmFnZXJ9Il0gPSAiICIuam9pbihsaWJyYXJpZXMuZ2V0KG1hbmFnZXIsIFtdKSkKCiAgICBpZiBpdGVtWyJjb25kYSJdOgogICAgICAgIHdhcm5pbmdzLndhcm4oImBjb25kYWAgcGFyYW1ldGVyIGlzIGRlcHJlY2F0ZWQiKQoKICAgICAgICAjIHNldCAiYXV0byIgY29uZGEgdmFsdWUKICAgICAgICBpZiBpdGVtWyJjb25kYSJdID09ICJhdXRvIjoKICAgICAgICAgICAgaXRlbVsiY29uZGEiXSA9ICJ0cnVlIiBpZiAiY29uZGEiIGluIGl0ZW1bInRveGVudiJdIGVsc2UgImZhbHNlIgoKICAgICAgICAjIGluamVjdCB0b3hkZXBzIGZvciBjb25kYQogICAgICAgIGlmIGl0ZW1bImNvbmRhIl0gPT0gInRydWUiIGFuZCAidG94LWNvbmRhIiBub3QgaW4gaXRlbVsidG94ZGVwcyJdLmxvd2VyKCk6CiAgICAgICAgICAgIGl0ZW1bInRveGRlcHMiXSA9ICgidG94LWNvbmRhICIgKyBpdGVtWyJ0b3hkZXBzIl0pLnN0cmlwKCkKCiAgICAjIG1ha2UgdGltZW91dC1taW51dGVzIGEgbnVtYmVyCiAgICBpdGVtWyJ0aW1lb3V0LW1pbnV0ZXMiXSA9IGludChpdGVtWyJ0aW1lb3V0LW1pbnV0ZXMiXSkKCiAgICAjIHZlcmlmeSB2YWx1ZXMKICAgIGFzc2VydCBpdGVtWyJweXRlc3QiXSBpbiB7InRydWUiLCAiZmFsc2UifQogICAgYXNzZXJ0IGl0ZW1bImNvbmRhIl0gaW4geyJ0cnVlIiwgImZhbHNlIn0KICAgIGFzc2VydCBpdGVtWyJkaXNwbGF5Il0gaW4geyJ0cnVlIiwgImZhbHNlIn0KCiAgICByZXR1cm4gaXRlbQoKCmlmIF9fbmFtZV9fID09ICJfX21haW5fXyI6CiAgICBsb2FkX3RveF90YXJnZXRzKCkK - - run: cat tox_matrix.py - - id: set-outputs - run: | # zizmor: ignore[template-injection] - uv run tox_matrix.py \ - --envs "${{ !inputs.fill && inputs.envs || format('{0}\n{1}', inputs.envs, steps.supported-pythons.outputs.envs) }}" \ - --libraries "${{ inputs.libraries }}" \ - --posargs "${{ inputs.posargs }}" --toxdeps "${{ inputs.toxdeps }}" \ - --toxargs "${{ inputs.toxargs }}" --pytest "${{ inputs.pytest }}" \ - --pytest-results-summary "${{ inputs.pytest-results-summary }}" \ - --coverage "${{ inputs.coverage }}" --conda "${{ inputs.conda }}" \ - --setenv "${{ inputs.setenv }}" \ - --display "${{ inputs.display }}" --cache-path "${{ inputs.cache-path }}" \ - --cache-key "${{ inputs.cache-key }}" --cache-restore-keys "${{ inputs.cache-restore-keys }}" \ - --artifact-path "${{ inputs.artifact-path }}" \ - --artifact-archive "${{ inputs.artifact-archive }}" \ - --artifact-include-hidden-files "${{ inputs.artifact-include-hidden-files }}" \ - --artifact-if-no-files-found "${{ inputs.artifact-if-no-files-found }}" \ - --runs-on "${{ inputs.runs-on }}" --default-python "${{ inputs.default_python }}" \ - --timeout-minutes "${{ inputs.timeout-minutes }}" - shell: sh - outputs: - matrix: ${{ steps.set-outputs.outputs.matrix }} - - - tox: - name: ${{ matrix.name }} - needs: [envs] - runs-on: ${{ matrix.os }} - timeout-minutes: ${{ matrix.timeout-minutes }} - strategy: - fail-fast: ${{ inputs.fail-fast }} - matrix: ${{fromJSON(needs.envs.outputs.matrix)}} - defaults: - run: - shell: bash -l {0} - working-directory: ${{ inputs.working-directory }} - steps: - - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - fetch-depth: 0 - lfs: true - submodules: ${{ inputs.submodules }} - ref: ${{ inputs.checkout_ref }} - persist-credentials: false - - - name: Cache ${{ matrix.cache_key }} - if: ${{ matrix.cache-path != '' && matrix.cache-key != '' }} - uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 - with: - path: ${{ matrix.cache-path }} - key: ${{ matrix.cache-key }} - restore-keys: ${{ matrix.cache-restore-keys }} - - - name: Install dependencies - uses: ConorMacBride/install-package@3e7ad059e07782ee54fa35f827df52aae0626f30 # v1.1.0 - with: - brew: ${{ matrix.libraries_brew }} - brew-cask: ${{ matrix.libraries_brew_cask }} - apt: ${{ matrix.libraries_apt }} - choco: ${{ matrix.libraries_choco }} - - - name: Setup Python ${{ matrix.python_version }} - if: ${{ matrix.conda != 'true' }} - uses: astral-sh/setup-uv@eac588ad8def6316056a12d4907a9d4d84ff7a3b # 7.3.0 - with: - version: "0.10.6" - python-version: ${{ matrix.python_version }} - activate-environment: "true" - ignore-empty-workdir: "true" - - - name: Setup conda (deprecated) - if: ${{ matrix.conda == 'true' }} - uses: mamba-org/setup-micromamba@add3a49764cedee8ee24e82dfde87f5bc2914462 # v2.0.7 - with: - environment-name: test - condarc: | - channels: - - conda-forge - use_uv: true - create-args: >- - conda - python=${{ matrix.python_version }} - uv=0.10.6 - init-shell: bash - cache-environment: true - cache-downloads: true - - - name: warn that using the `conda` parameter is deprecated - if: ${{ matrix.conda == 'true' }} - run: | - echo "> [!WARNING]" >> $GITHUB_STEP_SUMMARY - echo "> The conda parameter is deprecated (see https://github.com/OpenAstronomy/github-actions-workflows/issues/354)." >> $GITHUB_STEP_SUMMARY - - - id: set-env - if: ${{ matrix.setenv != '' }} - run: | - echo $SET_ENV_SCRIPT | base64 --decode > set_env.py - uv run set_env.py "${MATRIX_SETENV}" - rm set_env.py - env: - SET_ENV_SCRIPT: IyAvLy8gc2NyaXB0CiMgcmVxdWlyZXMtcHl0aG9uID0gIj09My4xMiIKIyBkZXBlbmRlbmNpZXMgPSBbCiMgICAgICJweXlhbWw9PTYuMC4yIiwKIyBdCiMgLy8vCmltcG9ydCBqc29uCmltcG9ydCBvcwppbXBvcnQgc3lzCgppbXBvcnQgeWFtbAoKR0lUSFVCX0VOViA9IG9zLmdldGVudigiR0lUSFVCX0VOViIpCmlmIEdJVEhVQl9FTlYgaXMgTm9uZToKICAgIHJhaXNlIFZhbHVlRXJyb3IoIkdJVEhVQl9FTlYgbm90IHNldC4gTXVzdCBiZSBydW4gaW5zaWRlIEdpdEh1YiBBY3Rpb25zLiIpCgpERUxJTUlURVIgPSAiRU9GIgoKCmRlZiBzZXRfZW52KGVudik6CgogICAgZW52ID0geWFtbC5sb2FkKGVudiwgTG9hZGVyPXlhbWwuQmFzZUxvYWRlcikKICAgIHByaW50KGpzb24uZHVtcHMoZW52LCBpbmRlbnQ9MikpCgogICAgaWYgbm90IGlzaW5zdGFuY2UoZW52LCBkaWN0KToKICAgICAgICB0aXRsZSA9ICJgZW52YCBtdXN0IGJlIG1hcHBpbmciCiAgICAgICAgbWVzc2FnZSA9IGYiYGVudmAgbXVzdCBiZSBtYXBwaW5nIG9mIGVudiB2YXJpYWJsZXMgdG8gdmFsdWVzLCBnb3QgdHlwZSB7dHlwZShlbnYpfSIKICAgICAgICBwcmludChmIjo6ZXJyb3IgdGl0bGU9e3RpdGxlfTo6e21lc3NhZ2V9IikKICAgICAgICBleGl0KDEpCgogICAgZm9yIGssIHYgaW4gZW52Lml0ZW1zKCk6CgogICAgICAgIGlmIG5vdCBpc2luc3RhbmNlKHYsIHN0cik6CiAgICAgICAgICAgIHRpdGxlID0gImBlbnZgIHZhbHVlcyBtdXN0IGJlIHN0cmluZ3MiCiAgICAgICAgICAgIG1lc3NhZ2UgPSBmImBlbnZgIHZhbHVlcyBtdXN0IGJlIHN0cmluZ3MsIGJ1dCB2YWx1ZSBvZiB7a30gaGFzIHR5cGUge3R5cGUodil9IgogICAgICAgICAgICBwcmludChmIjo6ZXJyb3IgdGl0bGU9e3RpdGxlfTo6e21lc3NhZ2V9IikKICAgICAgICAgICAgZXhpdCgxKQoKICAgICAgICB2ID0gdi5zcGxpdCgiXG4iKQoKICAgICAgICB3aXRoIG9wZW4oR0lUSFVCX0VOViwgImEiKSBhcyBmOgogICAgICAgICAgICBpZiBsZW4odikgPT0gMToKICAgICAgICAgICAgICAgIGYud3JpdGUoZiJ7a309e3ZbMF19XG4iKQogICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgZm9yIGxpbmUgaW4gdjoKICAgICAgICAgICAgICAgICAgICBhc3NlcnQgbGluZS5zdHJpcCgpICE9IERFTElNSVRFUgogICAgICAgICAgICAgICAgZi53cml0ZShmIntrfTw8e0RFTElNSVRFUn1cbiIpCiAgICAgICAgICAgICAgICBmb3IgbGluZSBpbiB2OgogICAgICAgICAgICAgICAgICAgIGYud3JpdGUoZiJ7bGluZX1cbiIpCiAgICAgICAgICAgICAgICBmLndyaXRlKGYie0RFTElNSVRFUn1cbiIpCgogICAgICAgIHByaW50KGYie2t9IHdyaXR0ZW4gdG8gR0lUSFVCX0VOViIpCgoKaWYgX19uYW1lX18gPT0gIl9fbWFpbl9fIjoKICAgIHNldF9lbnYoc3lzLmFyZ3ZbMV0pCg== - MATRIX_SETENV: ${{ matrix.setenv }} - - - name: Setup headless display - if: ${{ matrix.display == 'true' }} - uses: pyvista/setup-headless-display-action@5bc8de3bc71fcda7a96439571287a554901541a0 # v4.3 - - - run: uv pip install tox ${{ matrix.toxdeps }} # zizmor: ignore[template-injection] - - - run: tox -e ${{ matrix.toxenv }} ${{ matrix.toxargs }} -- ${{ matrix.pytest_flag }} ${{ matrix.posargs }} # zizmor: ignore[template-injection] - - - if: ${{ (success() || failure()) && matrix.artifact-path != '' }} - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 - with: - name: ${{ matrix.artifact-name }} - path: ${{ matrix.artifact-path }} - archive: ${{ matrix.artifact-archive }} - if-no-files-found: ${{ matrix.artifact-if-no-files-found }} - include-hidden-files: ${{ matrix.artifact-include-hidden-files }} - - - if: ${{ (success() || failure()) && matrix.pytest-results-summary == 'true' && matrix.pytest == 'true' }} - uses: test-summary/action@31493c76ec9e7aa675f1585d3ed6f1da69269a86 # v2.4 - with: - paths: "**/results.xml" - - - name: Upload to Codecov - # Even if tox fails, upload coverage - if: ${{ (success() || failure()) && contains(matrix.coverage, 'codecov') && matrix.pytest == 'true' }} - uses: codecov/codecov-action@1af58845a975a7985b0beb0cbe6fbbb71a41dbad # v5.5.3 - with: - token: ${{ secrets.CODECOV_TOKEN }} # zizmor: ignore[secrets-outside-env] - - - name: Upload coverage data to GitHub - id: upload-coverage-gh - if: ${{ (success() || failure()) && contains(matrix.coverage, 'github') && matrix.pytest == 'true' }} - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 - with: - name: coverage-data-${{ github.run_id }}-${{ runner.os }}-${{ runner.arch }}-${{ matrix.toxenv }} - path: .coverage.* - if-no-files-found: error - include-hidden-files: true - outputs: - coverage-gh: ${{ steps.upload-coverage-gh.outputs.artifact-id }} - - report_overall_test_coverage: - needs: [ tox ] - if: needs.tox.outputs.coverage-gh - name: report overall test coverage - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - lfs: true - submodules: ${{ inputs.submodules }} - ref: ${{ inputs.checkout_ref }} - persist-credentials: false - - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - - name: download coverage reports - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - with: - pattern: coverage-data-${{ github.run_id }}-* - merge-multiple: true - - uses: astral-sh/setup-uv@eac588ad8def6316056a12d4907a9d4d84ff7a3b # 7.3.0 - with: - ignore-empty-workdir: "true" - - name: generate coverage report - run: | - uvx coverage combine - uvx coverage report -i -m --format=markdown >> $GITHUB_STEP_SUMMARY - - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 - with: - name: coverage-report-${{ hashFiles('.coverage') }} - path: .coverage - if-no-files-found: error - include-hidden-files: true +name: Test Python package + +on: + workflow_call: + inputs: + envs: + description: Array of tox environments to test + required: true + type: string + fill: + description: Add an extra toxenv to the matrix for each current version of Python supported by the package + required: false + default: false + type: boolean + fill_platforms: + description: Platforms to iterate with fill + required: false + default: '' + type: string + fill_factors: + description: Tox factors to add to toxenvs added with `fill` + required: false + default: '' + type: string + libraries: + description: Additional packages to install + required: false + default: '' + type: string + posargs: + description: Positional arguments for the underlying tox test command + required: false + default: '' + type: string + toxdeps: + description: additional packages to install alongside tox (i.e. `tox-uv`) + required: false + default: '' + type: string + toxargs: + description: Positional arguments for tox + required: false + default: '' + type: string + pytest: + description: Whether pytest is run + required: false + default: true + type: boolean + pytest-results-summary: + description: Whether to report test summary + required: false + default: false + type: boolean + coverage: + description: Coverage providers to upload to + required: false + default: '' + type: string + conda: + description: Whether to test with conda (deprecated) + required: false + default: 'auto' + type: string + setenv: + description: A map of environment variables to be available when testing + required: false + default: '' + type: string + display: + description: Whether to setup a headless display + required: false + default: false + type: boolean + cache-path: + description: A list of files, directories, and wildcard patterns to cache and restore + required: false + default: '' + type: string + cache-key: + description: An explicit key for restoring and saving the cache + required: false + default: '' + type: string + cache-restore-keys: + description: An ordered list of keys to use for restoring the cache if no cache hit occurred for key + required: false + default: '' + type: string + artifact-path: + description: A list of files, directories, and wildcard patterns to upload as artifacts + required: false + default: '' + type: string + artifact-archive: + description: A flag to determine if the artifact is zipped, if false globs are not supported in archive-path. + required: false + default: true + type: boolean + artifact-include-hidden-files: + description: A flag to determine if the artifact includes hidden files. + required: false + default: false + type: boolean + artifact-if-no-files-found: + description: A flag to determine behavior if no matching files found. + required: false + default: 'warn' + type: string + runs-on: + description: Which runner image to use for each OS + required: false + default: '' + type: string + default_python: + description: Default version of Python + required: false + default: '3' + type: string + fail-fast: + description: Whether to cancel all in-progress jobs if any job fails + required: false + default: false + type: boolean + timeout-minutes: + description: The maximum number of minutes to let a job run before GitHub automatically cancels it + required: false + default: 360 + type: number + submodules: + description: Whether to checkout submodules + required: false + default: true + type: boolean + checkout_ref: + description: The ref to checkout + required: false + default: '' + type: string + working-directory: + description: The working directory for running tox, relative to the repository root + required: false + default: '.' + type: string + secrets: + CODECOV_TOKEN: + description: Codecov upload token + required: false + +jobs: + + envs: + name: Load tox environments + runs-on: ubuntu-latest + steps: + - uses: astral-sh/setup-uv@6ee6290f1cbc4156c0bdd66691b2c144ef8df19a # v7.4.0 + with: + version: "0.11.1" + enable-cache: false + ignore-empty-workdir: "true" + - if: inputs.fill + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: ${{ inputs.checkout_ref }} + persist-credentials: false + - if: inputs.fill + run: echo $SUPPORTED_PYTHONS_SCRIPT | base64 --decode > supported_pythons.py + env: + SUPPORTED_PYTHONS_SCRIPT: IyAvLy8gc2NyaXB0CiMgcmVxdWlyZXMtcHl0aG9uID0gIj49My4xMiIKIyBkZXBlbmRlbmNpZXMgPSBbCiMgICAgICJjbGljaz09OC4yLjEiLAojICAgICAicGFja2FnaW5nPT0yNS4wIiwKIyAgICAgInJlcXVlc3RzPT0yLjMyLjUiLAojICAgICAidG9tbGk9PTIuNC4wIiwKIyBdCiMgLy8vCmltcG9ydCBvcwppbXBvcnQgd2FybmluZ3MKZnJvbSBwYXRobGliIGltcG9ydCBQYXRoCgppbXBvcnQgY2xpY2sKaW1wb3J0IHJlcXVlc3RzCmltcG9ydCB0b21saQpmcm9tIHBhY2thZ2luZy5zcGVjaWZpZXJzIGltcG9ydCBTcGVjaWZpZXJTZXQKZnJvbSBwYWNrYWdpbmcudmVyc2lvbiBpbXBvcnQgVmVyc2lvbgoKCkBjbGljay5jb21tYW5kKCkKQGNsaWNrLm9wdGlvbigiLS1wYWNrYWdlLXNvdXJjZSIsIGRlZmF1bHQ9Tm9uZSkKQGNsaWNrLm9wdGlvbigiLS1mYWN0b3JzIiwgZGVmYXVsdD1Ob25lKQpAY2xpY2sub3B0aW9uKCItLW5vLWVvYXMiLCBpc19mbGFnPVRydWUsIGRlZmF1bHQ9RmFsc2UpCkBjbGljay5vcHRpb24oIi0tcGxhdGZvcm1zIiwgZGVmYXVsdD1Ob25lKQpkZWYgc3VwcG9ydGVkX3B5dGhvbl9lbnZzX2Jsb2NrKAogICAgcGFja2FnZV9zb3VyY2U6IFBhdGggPSBOb25lLAogICAgZmFjdG9yczogbGlzdFtzdHJdID0gTm9uZSwKICAgIG5vX2VvYXM6IGJvb2wgPSBGYWxzZSwKICAgIHBsYXRmb3JtczogbGlzdFtzdHJdID0gTm9uZSwKKToKICAgICIiImVudW1lcmF0ZSB0b3hlbnZzIGZvciBlYWNoIFB5dGhvbiB2ZXJzaW9uIHN1cHBvcnRlZCBieSBwYWNrYWdlIiIiCgogICAgaWYgcGxhdGZvcm1zIGlzIE5vbmU6CiAgICAgICAgcGxhdGZvcm1zID0gWyJsaW51eCJdCiAgICBlbGlmIGlzaW5zdGFuY2UocGxhdGZvcm1zLCBzdHIpOgogICAgICAgIHBsYXRmb3JtcyA9IHBsYXRmb3Jtcy5zcGxpdCgiLCIpCgogICAgdG94ZW52cyA9IHN1cHBvcnRlZF9weXRob25fdG94ZW52cyhwYWNrYWdlX3NvdXJjZSwgZmFjdG9ycywgbm9fZW9hcykKICAgIGVudnNfYmxvY2sgPSAiXFxuIi5qb2luKAogICAgICAgIGYiLSB7cGxhdGZvcm19OiB7dG94ZW52fSIgZm9yIHBsYXRmb3JtIGluIHBsYXRmb3JtcyBmb3IgdG94ZW52IGluIHRveGVudnMKICAgICkKCiAgICBwcmludChlbnZzX2Jsb2NrKQogICAgd2l0aCBvcGVuKG9zLmVudmlyb25bIkdJVEhVQl9PVVRQVVQiXSwgImEiKSBhcyBmOgogICAgICAgIGYud3JpdGUoZiJlbnZzPXtlbnZzX2Jsb2NrfVxuIikKCgpkZWYgc3VwcG9ydGVkX3B5dGhvbl90b3hlbnZzKAogICAgcGFja2FnZV9zb3VyY2U6IFBhdGggPSBOb25lLAogICAgZmFjdG9yczogbGlzdFtzdHJdID0gTm9uZSwKICAgIG5vX2VvYXM6IGJvb2wgPSBGYWxzZSwKKSAtPiBsaXN0W3N0cl06CiAgICBpZiBpc2luc3RhbmNlKGZhY3RvcnMsIHN0cik6CiAgICAgICAgZmFjdG9ycyA9IGZhY3RvcnMuc3BsaXQoIiwiKQoKICAgIHJldHVybiBbCiAgICAgICAgZiJweXtzdHIocHl0aG9uX3ZlcnNpb24pLnJlcGxhY2UoJy4nLCAnJyl9eyctJyArICctJy5qb2luKGZhY3RvcnMpIGlmIGZhY3RvcnMgaXMgbm90IE5vbmUgYW5kIGxlbihmYWN0b3JzKSA+IDAgZWxzZSAnJ30iCiAgICAgICAgZm9yIHB5dGhvbl92ZXJzaW9uIGluIHN1cHBvcnRlZF9weXRob25zKHBhY2thZ2Vfc291cmNlLCBub19lb2FzPW5vX2VvYXMpCiAgICBdCgoKZGVmIHN1cHBvcnRlZF9weXRob25zKAogICAgcGFja2FnZV9zb3VyY2U6IFBhdGggPSBOb25lLAogICAgbm9fZW9hczogYm9vbCA9IEZhbHNlLAopIC0+IGxpc3RbVmVyc2lvbl06CiAgICBjdXJyZW50X3B5dGhvbl92ZXJzaW9ucyA9IGN1cnJlbnRfcHl0aG9ucyhub19lb2FzPW5vX2VvYXMpCgogICAgaWYgbm90IHBhY2thZ2Vfc291cmNlOgogICAgICAgIHN1cHBvcnRlZF92ZXJzaW9ucyA9IGN1cnJlbnRfcHl0aG9uX3ZlcnNpb25zCiAgICBlbHNlOgogICAgICAgIHRyeToKICAgICAgICAgICAgcHlwcm9qZWN0X3RvbWxfZmlsZW5hbWUgPSBQYXRoKHBhY2thZ2Vfc291cmNlKSAvICJweXByb2plY3QudG9tbCIKICAgICAgICAgICAgaWYgcHlwcm9qZWN0X3RvbWxfZmlsZW5hbWUuZXhpc3RzKCk6CiAgICAgICAgICAgICAgICB3aXRoIG9wZW4ocHlwcm9qZWN0X3RvbWxfZmlsZW5hbWUsICJyYiIpIGFzIHB5cHJvamVjdF90b21sX2ZpbGU6CiAgICAgICAgICAgICAgICAgICAgcHlwcm9qZWN0X3RvbWwgPSB0b21saS5sb2FkKHB5cHJvamVjdF90b21sX2ZpbGUpCiAgICAgICAgICAgICAgICBpZiAicHJvamVjdCIgaW4gcHlwcm9qZWN0X3RvbWw6CiAgICAgICAgICAgICAgICAgICAgcHJvamVjdF9tZXRhZGF0YSA9IHB5cHJvamVjdF90b21sWyJ0YWJsZSJdCiAgICAgICAgICAgICAgICAgICAgaWYgInJlcXVpcmVzX3B5dGhvbiIgaW4gcHJvamVjdF9tZXRhZGF0YToKICAgICAgICAgICAgICAgICAgICAgICAgcHl0aG9uX3ZlcnNpb25fcmVxdWlyZW1lbnRzID0gU3BlY2lmaWVyU2V0KAogICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJvamVjdF9tZXRhZGF0YVsicmVxdWlyZXMtcHl0aG9uIl0KICAgICAgICAgICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICAgICAgICAgIHJhaXNlIEtleUVycm9yKAogICAgICAgICAgICAgICAgICAgICAgICAgICAgImBwcm9qZWN0LnJlcXVpcmVzX3B5dGhvbmAgbm90IGZvdW5kIGluIGBweXByb2plY3QudG9tbGA7IGVuc3VyZSB5b3VyIHBhY2thZ2UgY29uZm9ybXMgdG8gUEVQNjIxIgogICAgICAgICAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgICAgIHJhaXNlIEtleUVycm9yKAogICAgICAgICAgICAgICAgICAgICAgICAiYHByb2plY3RgIG5vdCBmb3VuZCBpbiBgcHlwcm9qZWN0LnRvbWxgOyBlbnN1cmUgeW91ciBwYWNrYWdlIGNvbmZvcm1zIHRvIFBFUDYyMSIKICAgICAgICAgICAgICAgICAgICApCiAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICByYWlzZSBGaWxlTm90Rm91bmRFcnJvcigKICAgICAgICAgICAgICAgICAgICAiY291bGQgbm90IGZpbmQgYHB5cHJvamVjdC50b21sYCBpbiB0aGUgcHJvdmlkZWQgcGFja2FnZSBzb3VyY2U7IGVuc3VyZSB5b3VyIHBhY2thZ2UgY29uZm9ybXMgdG8gUEVQNjIxIgogICAgICAgICAgICAgICAgKQoKICAgICAgICAgICAgc3VwcG9ydGVkX3ZlcnNpb25zID0gWwogICAgICAgICAgICAgICAgcHl0aG9uX3ZlcnNpb24KICAgICAgICAgICAgICAgIGZvciBweXRob25fdmVyc2lvbiBpbiBjdXJyZW50X3B5dGhvbl92ZXJzaW9ucwogICAgICAgICAgICAgICAgaWYgcHl0aG9uX3ZlcnNpb24gaW4gcHl0aG9uX3ZlcnNpb25fcmVxdWlyZW1lbnRzCiAgICAgICAgICAgIF0KICAgICAgICBleGNlcHQgKEtleUVycm9yLCBUeXBlRXJyb3IsIEZpbGVOb3RGb3VuZEVycm9yKSBhcyBlcnJvcjoKICAgICAgICAgICAgd2FybmluZ3Mud2FybihzdHIoZXJyb3IpKQogICAgICAgICAgICB3YXJuaW5ncy53YXJuKCJmYWxsaW5nIGJhY2sgdG8gY3VycmVudCBQeXRob24gdmVyc2lvbnMuLi4iKQogICAgICAgICAgICBzdXBwb3J0ZWRfdmVyc2lvbnMgPSBjdXJyZW50X3B5dGhvbl92ZXJzaW9ucwoKICAgIHJldHVybiBzdXBwb3J0ZWRfdmVyc2lvbnMKCgpkZWYgY3VycmVudF9weXRob25zKG5vX2VvYXM6IGJvb2wgPSBGYWxzZSkgLT4gbGlzdFtWZXJzaW9uXToKICAgIHVybCA9ICJodHRwczovL2VuZG9mbGlmZS5kYXRlL2FwaS92MS9wcm9kdWN0cy9weXRob24iCiAgICByZXNwb25zZSA9IHJlcXVlc3RzLmdldCgiaHR0cHM6Ly9lbmRvZmxpZmUuZGF0ZS9hcGkvdjEvcHJvZHVjdHMvcHl0aG9uIikKICAgIGlmIHJlc3BvbnNlLnN0YXR1c19jb2RlID09IDIwMDoKICAgICAgICByZXR1cm4gWwogICAgICAgICAgICBWZXJzaW9uKHB5dGhvbl92ZXJzaW9uWyJuYW1lIl0pCiAgICAgICAgICAgIGZvciBweXRob25fdmVyc2lvbiBpbiByZXNwb25zZS5qc29uKClbInJlc3VsdCJdWyJyZWxlYXNlcyJdCiAgICAgICAgICAgIGlmIG5vdCBweXRob25fdmVyc2lvblsiaXNFb2FzIiBpZiBub19lb2FzIGVsc2UgImlzRW9sIl0KICAgICAgICBdCiAgICBlbHNlOgogICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoZiJyZXF1ZXN0IHRvIHt1cmx9IHJldHVybmVkIHN0YXR1cyBjb2RlIHtyZXNwb25zZS5zdGF0dXNfY29kZX0iKQoKCmlmIF9fbmFtZV9fID09ICJfX21haW5fXyI6CiAgICBzdXBwb3J0ZWRfcHl0aG9uX2VudnNfYmxvY2soKQo= + - if: inputs.fill # zizmor: ignore[template-injection] + id: supported-pythons + run: uv run supported_pythons.py --package-source . ${{ inputs.fill_platforms != '' && format('--platforms {0}', inputs.fill_platforms) || '' }} ${{ inputs.fill_factors != '' && format('--factors {0}', inputs.fill_factors) || '' }} + shell: sh + - run: echo $TOX_MATRIX_SCRIPT | base64 --decode > tox_matrix.py + env: + TOX_MATRIX_SCRIPT: IyAvLy8gc2NyaXB0CiMgcmVxdWlyZXMtcHl0aG9uID0gIj09My4xMiIKIyBkZXBlbmRlbmNpZXMgPSBbCiMgICAgICJjbGljaz09OC4yLjEiLAojICAgICAicHl5YW1sPT02LjAuMiIsCiMgXQojIC8vLwppbXBvcnQganNvbgppbXBvcnQgb3MKaW1wb3J0IHJlCmltcG9ydCB3YXJuaW5ncwoKaW1wb3J0IGNsaWNrCmltcG9ydCB5YW1sCgoKQGNsaWNrLmNvbW1hbmQoKQpAY2xpY2sub3B0aW9uKCItLWVudnMiLCBkZWZhdWx0PSIiKQpAY2xpY2sub3B0aW9uKCItLWxpYnJhcmllcyIsIGRlZmF1bHQ9IiIpCkBjbGljay5vcHRpb24oIi0tcG9zYXJncyIsIGRlZmF1bHQ9IiIpCkBjbGljay5vcHRpb24oIi0tdG94ZGVwcyIsIGRlZmF1bHQ9IiIpCkBjbGljay5vcHRpb24oIi0tdG94YXJncyIsIGRlZmF1bHQ9IiIpCkBjbGljay5vcHRpb24oIi0tcHl0ZXN0IiwgZGVmYXVsdD0idHJ1ZSIpCkBjbGljay5vcHRpb24oIi0tcHl0ZXN0LXJlc3VsdHMtc3VtbWFyeSIsIGRlZmF1bHQ9ImZhbHNlIikKQGNsaWNrLm9wdGlvbigiLS1jb3ZlcmFnZSIsIGRlZmF1bHQ9IiIpCkBjbGljay5vcHRpb24oIi0tY29uZGEiLCBkZWZhdWx0PSJhdXRvIikKQGNsaWNrLm9wdGlvbigiLS1zZXRlbnYiLCBkZWZhdWx0PSIiKQpAY2xpY2sub3B0aW9uKCItLWRpc3BsYXkiLCBkZWZhdWx0PSJmYWxzZSIpCkBjbGljay5vcHRpb24oIi0tY2FjaGUtcGF0aCIsIGRlZmF1bHQ9IiIpCkBjbGljay5vcHRpb24oIi0tY2FjaGUta2V5IiwgZGVmYXVsdD0iIikKQGNsaWNrLm9wdGlvbigiLS1jYWNoZS1yZXN0b3JlLWtleXMiLCBkZWZhdWx0PSIiKQpAY2xpY2sub3B0aW9uKCItLWFydGlmYWN0LXBhdGgiLCBkZWZhdWx0PSIiKQpAY2xpY2sub3B0aW9uKCItLWFydGlmYWN0LWFyY2hpdmUiLCBkZWZhdWx0PSJ0cnVlIikKQGNsaWNrLm9wdGlvbigiLS1hcnRpZmFjdC1pbmNsdWRlLWhpZGRlbi1maWxlcyIsIGRlZmF1bHQ9ImZhbHNlIikKQGNsaWNrLm9wdGlvbigiLS1hcnRpZmFjdC1pZi1uby1maWxlcy1mb3VuZCIsIGRlZmF1bHQ9Indhcm4iKQpAY2xpY2sub3B0aW9uKCItLXJ1bnMtb24iLCBkZWZhdWx0PSIiKQpAY2xpY2sub3B0aW9uKCItLWRlZmF1bHQtcHl0aG9uIiwgZGVmYXVsdD0iIikKQGNsaWNrLm9wdGlvbigiLS10aW1lb3V0LW1pbnV0ZXMiLCBkZWZhdWx0PSIzNjAiKQpkZWYgbG9hZF90b3hfdGFyZ2V0cygKICAgIGVudnMsCiAgICBsaWJyYXJpZXMsCiAgICBwb3NhcmdzLAogICAgdG94ZGVwcywKICAgIHRveGFyZ3MsCiAgICBweXRlc3QsCiAgICBweXRlc3RfcmVzdWx0c19zdW1tYXJ5LAogICAgY292ZXJhZ2UsCiAgICBjb25kYSwKICAgIHNldGVudiwKICAgIGRpc3BsYXksCiAgICBjYWNoZV9wYXRoLAogICAgY2FjaGVfa2V5LAogICAgY2FjaGVfcmVzdG9yZV9rZXlzLAogICAgYXJ0aWZhY3RfcGF0aCwKICAgIGFydGlmYWN0X2FyY2hpdmUsCiAgICBhcnRpZmFjdF9pbmNsdWRlX2hpZGRlbl9maWxlcywKICAgIGFydGlmYWN0X2lmX25vX2ZpbGVzX2ZvdW5kLAogICAgcnVuc19vbiwKICAgIGRlZmF1bHRfcHl0aG9uLAogICAgdGltZW91dF9taW51dGVzLAopOgogICAgIiIiU2NyaXB0IHRvIGxvYWQgdG94IHRhcmdldHMgZm9yIEdpdEh1YiBBY3Rpb25zIHdvcmtmbG93LiIiIgogICAgIyBMb2FkIGVudnMgY29uZmlnCiAgICBlbnZzID0geWFtbC5sb2FkKGVudnMucmVwbGFjZSgiXFxuIiwgIlxuIiksIExvYWRlcj15YW1sLkJhc2VMb2FkZXIpCiAgICBwcmludChqc29uLmR1bXBzKGVudnMsIGluZGVudD0yKSkKCiAgICAjIExvYWQgZ2xvYmFsIGxpYnJhcmllcyBjb25maWcKICAgIGdsb2JhbF9saWJyYXJpZXMgPSB7CiAgICAgICAgImJyZXciOiBbXSwKICAgICAgICAiYnJldy1jYXNrIjogW10sCiAgICAgICAgImFwdCI6IFtdLAogICAgICAgICJjaG9jbyI6IFtdLAogICAgfQogICAgbGlicmFyaWVzID0geWFtbC5sb2FkKGxpYnJhcmllcywgTG9hZGVyPXlhbWwuQmFzZUxvYWRlcikKICAgIGlmIGxpYnJhcmllcyBpcyBub3QgTm9uZToKICAgICAgICBnbG9iYWxfbGlicmFyaWVzLnVwZGF0ZShsaWJyYXJpZXMpCiAgICBwcmludChqc29uLmR1bXBzKGdsb2JhbF9saWJyYXJpZXMsIGluZGVudD0yKSkKCiAgICAjIERlZmF1bHQgaW1hZ2VzIHRvIHVzZSBmb3IgcnVubmVycwogICAgZGVmYXVsdF9ydW5zX29uID0gewogICAgICAgICJsaW51eCI6ICJ1YnVudHUtbGF0ZXN0IiwKICAgICAgICAibWFjb3MiOiAibWFjb3MtbGF0ZXN0IiwKICAgICAgICAid2luZG93cyI6ICJ3aW5kb3dzLWxhdGVzdCIsCiAgICB9CiAgICBjdXN0b21fcnVuc19vbiA9IHlhbWwubG9hZChydW5zX29uLCBMb2FkZXI9eWFtbC5CYXNlTG9hZGVyKQogICAgaWYgaXNpbnN0YW5jZShjdXN0b21fcnVuc19vbiwgZGljdCk6CiAgICAgICAgZGVmYXVsdF9ydW5zX29uLnVwZGF0ZShjdXN0b21fcnVuc19vbikKICAgIHByaW50KGpzb24uZHVtcHMoZGVmYXVsdF9ydW5zX29uLCBpbmRlbnQ9MikpCgogICAgIyBEZWZhdWx0IHN0cmluZyBwYXJhbWV0ZXJzIHdoaWNoIGNhbiBiZSBvdmVyd3JpdHRlbiBieSBlYWNoIGVudgogICAgc3RyaW5nX3BhcmFtZXRlcnMgPSB7CiAgICAgICAgInBvc2FyZ3MiOiBwb3NhcmdzLAogICAgICAgICJ0b3hkZXBzIjogdG94ZGVwcywKICAgICAgICAidG94YXJncyI6IHRveGFyZ3MsCiAgICAgICAgInB5dGVzdCI6IHB5dGVzdCwKICAgICAgICAicHl0ZXN0LXJlc3VsdHMtc3VtbWFyeSI6IHB5dGVzdF9yZXN1bHRzX3N1bW1hcnksCiAgICAgICAgImNvdmVyYWdlIjogY292ZXJhZ2UsCiAgICAgICAgImNvbmRhIjogY29uZGEsCiAgICAgICAgInNldGVudiI6IHNldGVudiwKICAgICAgICAiZGlzcGxheSI6IGRpc3BsYXksCiAgICAgICAgImNhY2hlLXBhdGgiOiBjYWNoZV9wYXRoLAogICAgICAgICJjYWNoZS1rZXkiOiBjYWNoZV9rZXksCiAgICAgICAgImNhY2hlLXJlc3RvcmUta2V5cyI6IGNhY2hlX3Jlc3RvcmVfa2V5cywKICAgICAgICAiYXJ0aWZhY3QtcGF0aCI6IGFydGlmYWN0X3BhdGgsCiAgICAgICAgImFydGlmYWN0LWFyY2hpdmUiOiBhcnRpZmFjdF9hcmNoaXZlLAogICAgICAgICJhcnRpZmFjdC1pbmNsdWRlLWhpZGRlbi1maWxlcyI6IGFydGlmYWN0X2luY2x1ZGVfaGlkZGVuX2ZpbGVzLAogICAgICAgICJhcnRpZmFjdC1pZi1uby1maWxlcy1mb3VuZCI6IGFydGlmYWN0X2lmX25vX2ZpbGVzX2ZvdW5kLAogICAgICAgICJ0aW1lb3V0LW1pbnV0ZXMiOiB0aW1lb3V0X21pbnV0ZXMsCiAgICB9CgogICAgIyBDcmVhdGUgbWF0cml4CiAgICBtYXRyaXggPSB7ImluY2x1ZGUiOiBbXX0KICAgIGZvciBlbnYgaW4gZW52czoKICAgICAgICBtYXRyaXhbImluY2x1ZGUiXS5hcHBlbmQoCiAgICAgICAgICAgIGdldF9tYXRyaXhfaXRlbSgKICAgICAgICAgICAgICAgIGVudiwKICAgICAgICAgICAgICAgIGdsb2JhbF9saWJyYXJpZXM9Z2xvYmFsX2xpYnJhcmllcywKICAgICAgICAgICAgICAgIGdsb2JhbF9zdHJpbmdfcGFyYW1ldGVycz1zdHJpbmdfcGFyYW1ldGVycywKICAgICAgICAgICAgICAgIHJ1bnNfb249ZGVmYXVsdF9ydW5zX29uLAogICAgICAgICAgICAgICAgZGVmYXVsdF9weXRob249ZGVmYXVsdF9weXRob24sCiAgICAgICAgICAgICkKICAgICAgICApCgogICAgIyBPdXRwdXQgbWF0cml4CiAgICBwcmludChqc29uLmR1bXBzKG1hdHJpeCwgaW5kZW50PTIpKQogICAgd2l0aCBvcGVuKG9zLmVudmlyb25bIkdJVEhVQl9PVVRQVVQiXSwgImEiKSBhcyBmOgogICAgICAgIGYud3JpdGUoZiJtYXRyaXg9e2pzb24uZHVtcHMobWF0cml4KX1cbiIpCgoKZGVmIGdldF9tYXRyaXhfaXRlbShlbnYsIGdsb2JhbF9saWJyYXJpZXMsIGdsb2JhbF9zdHJpbmdfcGFyYW1ldGVycywgcnVuc19vbiwgZGVmYXVsdF9weXRob24pOgoKICAgICMgZGVmaW5lIHNwZWMgZm9yIGVhY2ggbWF0cml4IGluY2x1ZGUgKCsgZ2xvYmFsX3N0cmluZ19wYXJhbWV0ZXJzKQogICAgaXRlbSA9IHsKICAgICAgICAib3MiOiBOb25lLAogICAgICAgICJ0b3hlbnYiOiBOb25lLAogICAgICAgICJweXRob25fdmVyc2lvbiI6IE5vbmUsCiAgICAgICAgIm5hbWUiOiBOb25lLAogICAgICAgICJweXRlc3RfZmxhZyI6IE5vbmUsCiAgICAgICAgImxpYnJhcmllc19icmV3IjogTm9uZSwKICAgICAgICAibGlicmFyaWVzX2JyZXdfY2FzayI6IE5vbmUsCiAgICAgICAgImxpYnJhcmllc19hcHQiOiBOb25lLAogICAgICAgICJsaWJyYXJpZXNfY2hvY28iOiBOb25lLAogICAgICAgICJjYWNoZS1wYXRoIjogTm9uZSwKICAgICAgICAiY2FjaGUta2V5IjogTm9uZSwKICAgICAgICAiY2FjaGUtcmVzdG9yZS1rZXlzIjogTm9uZSwKICAgICAgICAiYXJ0aWZhY3QtbmFtZSI6IE5vbmUsCiAgICAgICAgImFydGlmYWN0LXBhdGgiOiBOb25lLAogICAgICAgICJhcnRpZmFjdC1hcmNoaXZlIjogTm9uZSwKICAgICAgICAiYXJ0aWZhY3QtaW5jbHVkZS1oaWRkZW4tZmlsZXMiOiBOb25lLAogICAgICAgICJhcnRpZmFjdC1pZi1uby1maWxlcy1mb3VuZCI6IE5vbmUsCiAgICAgICAgInRpbWVvdXQtbWludXRlcyI6IE5vbmUsCiAgICB9CiAgICBmb3Igc3RyaW5nX3BhcmFtLCBkZWZhdWx0IGluIGdsb2JhbF9zdHJpbmdfcGFyYW1ldGVycy5pdGVtcygpOgogICAgICAgIGVudl92YWx1ZSA9IGVudi5nZXQoc3RyaW5nX3BhcmFtKQogICAgICAgIGl0ZW1bc3RyaW5nX3BhcmFtXSA9IGRlZmF1bHQgaWYgZW52X3ZhbHVlIGlzIE5vbmUgZWxzZSBlbnZfdmFsdWUKCiAgICAjIHNldCBvcyBhbmQgdG94ZW52CiAgICBmb3IgaywgdiBpbiBydW5zX29uLml0ZW1zKCk6CiAgICAgICAgaWYgayBpbiBlbnY6CiAgICAgICAgICAgIHBsYXRmb3JtID0gawogICAgICAgICAgICBpdGVtWyJvcyJdID0gZW52LmdldCgicnVucy1vbiIsIHYpCiAgICAgICAgICAgIGl0ZW1bInRveGVudiJdID0gZW52W2tdCiAgICBhc3NlcnQgaXRlbVsib3MiXSBpcyBub3QgTm9uZSBhbmQgaXRlbVsidG94ZW52Il0gaXMgbm90IE5vbmUKCiAgICAjIHNldCBweXRob25fdmVyc2lvbgogICAgcHl0aG9uX3ZlcnNpb24gPSBlbnYuZ2V0KCJweXRob24tdmVyc2lvbiIpCiAgICBtID0gcmUuc2VhcmNoKCJecHkoMnwzKShbMC05XSt0PykiLCBpdGVtWyJ0b3hlbnYiXSkKICAgIGlmIHB5dGhvbl92ZXJzaW9uIGlzIG5vdCBOb25lOgogICAgICAgIGl0ZW1bInB5dGhvbl92ZXJzaW9uIl0gPSBweXRob25fdmVyc2lvbgogICAgZWxpZiBtIGlzIG5vdCBOb25lOgogICAgICAgIG1ham9yLCBtaW5vciA9IG0uZ3JvdXBzKCkKICAgICAgICBpdGVtWyJweXRob25fdmVyc2lvbiJdID0gZiJ7bWFqb3J9LnttaW5vcn0iCiAgICBlbHNlOgogICAgICAgIGl0ZW1bInB5dGhvbl92ZXJzaW9uIl0gPSBlbnYuZ2V0KCJkZWZhdWx0X3B5dGhvbiIpIG9yIGRlZmF1bHRfcHl0aG9uCgogICAgIyBzZXQgbmFtZQogICAgaXRlbVsibmFtZSJdID0gZW52LmdldCgibmFtZSIpIG9yIGYie2l0ZW1bJ3RveGVudiddfSAoe2l0ZW1bJ29zJ119KSIKCiAgICAjIHNldCBhcnRpZmFjdC1uYW1lIChyZXBsYWNlIGludmFsaWQgcGF0aCBjaGFyYWN0ZXJzKQogICAgaXRlbVsiYXJ0aWZhY3QtbmFtZSJdID0gcmUuc3ViKHIiW1xcIC86PD58Kj9cIiddIiwgIi0iLCBpdGVtWyJuYW1lIl0pCiAgICBpdGVtWyJhcnRpZmFjdC1uYW1lIl0gPSByZS5zdWIociItKyIsICItIiwgaXRlbVsiYXJ0aWZhY3QtbmFtZSJdKQoKICAgICMgc2V0IHB5dGVzdF9mbGFnCiAgICBpdGVtWyJweXRlc3RfZmxhZyJdID0gIiIKICAgIHNlcCA9IHIiXFwiIGlmIHBsYXRmb3JtID09ICJ3aW5kb3dzIiBlbHNlICIvIgogICAgaWYgaXRlbVsicHl0ZXN0Il0gPT0gInRydWUiOgogICAgICAgIGlmICJjb2RlY292IiBpbiBpdGVtLmdldCgiY292ZXJhZ2UiLCAiIik6CiAgICAgICAgICAgICMgTm90ZSB0aGF0IHdlIGRvbid0IGluY2x1ZGUgLS1jb3YgaGVyZSBhcyBpZiBpdCdzIHByb3ZpZGVkIHRvIHB5dGVzdCB0d2ljZSBpdCBicmVha3MgY292IHJlcG9ydGluZy4KICAgICAgICAgICAgIyBMb3RzIG9mIHVzZXJzIG9mIHRoaXMgc3BlY2lmeSAtLWNvdiBpbiB0aGVpciB0b3guaW5pIHNvIGl0J3MgYmVlbiByZW1vdmVkIGZvciBiYWNrd2FyZHMgY29tcGF0aWJpbGl0eS4KICAgICAgICAgICAgIyBodHRwczovL2dpdGh1Yi5jb20vT3BlbkFzdHJvbm9teS9naXRodWItYWN0aW9ucy13b3JrZmxvd3MvaXNzdWVzLzM4MwogICAgICAgICAgICBpdGVtWyJweXRlc3RfZmxhZyJdICs9ICgKICAgICAgICAgICAgICAgIHJmIi0tY292LXJlcG9ydD14bWw6JHt7R0lUSFVCX1dPUktTUEFDRX19e3NlcH1jb3ZlcmFnZS54bWwgIgogICAgICAgICAgICApCgogICAgICAgIGlmIGl0ZW1bInB5dGVzdC1yZXN1bHRzLXN1bW1hcnkiXSA9PSAidHJ1ZSI6CiAgICAgICAgICAgIGl0ZW1bInB5dGVzdF9mbGFnIl0gKz0gcmYiLS1qdW5pdHhtbCAke3tHSVRIVUJfV09SS1NQQUNFfX17c2VwfXJlc3VsdHMueG1sICIKCiAgICAjIHNldCBsaWJyYXJpZXMKICAgIGVudl9saWJyYXJpZXMgPSBlbnYuZ2V0KCJsaWJyYXJpZXMiKQogICAgaWYgaXNpbnN0YW5jZShlbnZfbGlicmFyaWVzLCBzdHIpIGFuZCBsZW4oZW52X2xpYnJhcmllcy5zdHJpcCgpKSA9PSAwOgogICAgICAgIGVudl9saWJyYXJpZXMgPSB7fSAgIyBubyBsaWJyYXJpZXMgcmVxdWVzdGVkIGZvciBlbnZpcm9ubWVudAogICAgbGlicmFyaWVzID0gZ2xvYmFsX2xpYnJhcmllcyBpZiBlbnZfbGlicmFyaWVzIGlzIE5vbmUgZWxzZSBlbnZfbGlicmFyaWVzCiAgICBmb3IgbWFuYWdlciBpbiBbImJyZXciLCAiYnJld19jYXNrIiwgImFwdCIsICJjaG9jbyJdOgogICAgICAgIGl0ZW1bZiJsaWJyYXJpZXNfe21hbmFnZXJ9Il0gPSAiICIuam9pbihsaWJyYXJpZXMuZ2V0KG1hbmFnZXIsIFtdKSkKCiAgICBpZiBpdGVtWyJjb25kYSJdOgogICAgICAgIHdhcm5pbmdzLndhcm4oImBjb25kYWAgcGFyYW1ldGVyIGlzIGRlcHJlY2F0ZWQiKQoKICAgICAgICAjIHNldCAiYXV0byIgY29uZGEgdmFsdWUKICAgICAgICBpZiBpdGVtWyJjb25kYSJdID09ICJhdXRvIjoKICAgICAgICAgICAgaXRlbVsiY29uZGEiXSA9ICJ0cnVlIiBpZiAiY29uZGEiIGluIGl0ZW1bInRveGVudiJdIGVsc2UgImZhbHNlIgoKICAgICAgICAjIGluamVjdCB0b3hkZXBzIGZvciBjb25kYQogICAgICAgIGlmIGl0ZW1bImNvbmRhIl0gPT0gInRydWUiIGFuZCAidG94LWNvbmRhIiBub3QgaW4gaXRlbVsidG94ZGVwcyJdLmxvd2VyKCk6CiAgICAgICAgICAgIGl0ZW1bInRveGRlcHMiXSA9ICgidG94LWNvbmRhICIgKyBpdGVtWyJ0b3hkZXBzIl0pLnN0cmlwKCkKCiAgICAjIG1ha2UgdGltZW91dC1taW51dGVzIGEgbnVtYmVyCiAgICBpdGVtWyJ0aW1lb3V0LW1pbnV0ZXMiXSA9IGludChpdGVtWyJ0aW1lb3V0LW1pbnV0ZXMiXSkKCiAgICAjIHZlcmlmeSB2YWx1ZXMKICAgIGFzc2VydCBpdGVtWyJweXRlc3QiXSBpbiB7InRydWUiLCAiZmFsc2UifQogICAgYXNzZXJ0IGl0ZW1bImNvbmRhIl0gaW4geyJ0cnVlIiwgImZhbHNlIn0KICAgIGFzc2VydCBpdGVtWyJkaXNwbGF5Il0gaW4geyJ0cnVlIiwgImZhbHNlIn0KCiAgICByZXR1cm4gaXRlbQoKCmlmIF9fbmFtZV9fID09ICJfX21haW5fXyI6CiAgICBsb2FkX3RveF90YXJnZXRzKCkK + - run: cat tox_matrix.py + - id: set-outputs + run: | # zizmor: ignore[template-injection] + uv run tox_matrix.py \ + --envs "${{ !inputs.fill && inputs.envs || format('{0}\n{1}', inputs.envs, steps.supported-pythons.outputs.envs) }}" \ + --libraries "${{ inputs.libraries }}" \ + --posargs "${{ inputs.posargs }}" --toxdeps "${{ inputs.toxdeps }}" \ + --toxargs "${{ inputs.toxargs }}" --pytest "${{ inputs.pytest }}" \ + --pytest-results-summary "${{ inputs.pytest-results-summary }}" \ + --coverage "${{ inputs.coverage }}" --conda "${{ inputs.conda }}" \ + --setenv "${{ inputs.setenv }}" \ + --display "${{ inputs.display }}" --cache-path "${{ inputs.cache-path }}" \ + --cache-key "${{ inputs.cache-key }}" --cache-restore-keys "${{ inputs.cache-restore-keys }}" \ + --artifact-path "${{ inputs.artifact-path }}" \ + --artifact-archive "${{ inputs.artifact-archive }}" \ + --artifact-include-hidden-files "${{ inputs.artifact-include-hidden-files }}" \ + --artifact-if-no-files-found "${{ inputs.artifact-if-no-files-found }}" \ + --runs-on "${{ inputs.runs-on }}" --default-python "${{ inputs.default_python }}" \ + --timeout-minutes "${{ inputs.timeout-minutes }}" + shell: sh + outputs: + matrix: ${{ steps.set-outputs.outputs.matrix }} + + + tox: + name: ${{ matrix.name }} + needs: [envs] + runs-on: ${{ matrix.os }} + timeout-minutes: ${{ matrix.timeout-minutes }} + strategy: + fail-fast: ${{ inputs.fail-fast }} + matrix: ${{fromJSON(needs.envs.outputs.matrix)}} + defaults: + run: + shell: bash -l {0} + working-directory: ${{ inputs.working-directory }} + steps: + + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + lfs: true + submodules: ${{ inputs.submodules }} + ref: ${{ inputs.checkout_ref }} + persist-credentials: false + + - name: Cache ${{ matrix.cache_key }} + if: ${{ matrix.cache-path != '' && matrix.cache-key != '' }} + uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 + with: + path: ${{ matrix.cache-path }} + key: ${{ matrix.cache-key }} + restore-keys: ${{ matrix.cache-restore-keys }} + + - name: Install dependencies + uses: ConorMacBride/install-package@3e7ad059e07782ee54fa35f827df52aae0626f30 # v1.1.0 + with: + brew: ${{ matrix.libraries_brew }} + brew-cask: ${{ matrix.libraries_brew_cask }} + apt: ${{ matrix.libraries_apt }} + choco: ${{ matrix.libraries_choco }} + + - name: Setup Python ${{ matrix.python_version }} + if: ${{ matrix.conda != 'true' }} + uses: astral-sh/setup-uv@6ee6290f1cbc4156c0bdd66691b2c144ef8df19a # v7.4.0 + with: + version: "0.11.1" + python-version: ${{ matrix.python_version }} + activate-environment: "true" + ignore-empty-workdir: "true" + + - name: Setup conda (deprecated) + if: ${{ matrix.conda == 'true' }} + uses: mamba-org/setup-micromamba@add3a49764cedee8ee24e82dfde87f5bc2914462 # v2.0.7 + with: + environment-name: test + condarc: | + channels: + - conda-forge + use_uv: true + create-args: >- + conda + python=${{ matrix.python_version }} + uv=0.10.6 + init-shell: bash + cache-environment: true + cache-downloads: true + + - name: warn that using the `conda` parameter is deprecated + if: ${{ matrix.conda == 'true' }} + run: | + echo "> [!WARNING]" >> $GITHUB_STEP_SUMMARY + echo "> The conda parameter is deprecated (see https://github.com/OpenAstronomy/github-actions-workflows/issues/354)." >> $GITHUB_STEP_SUMMARY + + - id: set-env + if: ${{ matrix.setenv != '' }} + run: | + echo $SET_ENV_SCRIPT | base64 --decode > set_env.py + uv run set_env.py "${MATRIX_SETENV}" + rm set_env.py + env: + SET_ENV_SCRIPT: IyAvLy8gc2NyaXB0CiMgcmVxdWlyZXMtcHl0aG9uID0gIj09My4xMiIKIyBkZXBlbmRlbmNpZXMgPSBbCiMgICAgICJweXlhbWw9PTYuMC4yIiwKIyBdCiMgLy8vCmltcG9ydCBqc29uCmltcG9ydCBvcwppbXBvcnQgc3lzCgppbXBvcnQgeWFtbAoKR0lUSFVCX0VOViA9IG9zLmdldGVudigiR0lUSFVCX0VOViIpCmlmIEdJVEhVQl9FTlYgaXMgTm9uZToKICAgIHJhaXNlIFZhbHVlRXJyb3IoIkdJVEhVQl9FTlYgbm90IHNldC4gTXVzdCBiZSBydW4gaW5zaWRlIEdpdEh1YiBBY3Rpb25zLiIpCgpERUxJTUlURVIgPSAiRU9GIgoKCmRlZiBzZXRfZW52KGVudik6CgogICAgZW52ID0geWFtbC5sb2FkKGVudiwgTG9hZGVyPXlhbWwuQmFzZUxvYWRlcikKICAgIHByaW50KGpzb24uZHVtcHMoZW52LCBpbmRlbnQ9MikpCgogICAgaWYgbm90IGlzaW5zdGFuY2UoZW52LCBkaWN0KToKICAgICAgICB0aXRsZSA9ICJgZW52YCBtdXN0IGJlIG1hcHBpbmciCiAgICAgICAgbWVzc2FnZSA9IGYiYGVudmAgbXVzdCBiZSBtYXBwaW5nIG9mIGVudiB2YXJpYWJsZXMgdG8gdmFsdWVzLCBnb3QgdHlwZSB7dHlwZShlbnYpfSIKICAgICAgICBwcmludChmIjo6ZXJyb3IgdGl0bGU9e3RpdGxlfTo6e21lc3NhZ2V9IikKICAgICAgICBleGl0KDEpCgogICAgZm9yIGssIHYgaW4gZW52Lml0ZW1zKCk6CgogICAgICAgIGlmIG5vdCBpc2luc3RhbmNlKHYsIHN0cik6CiAgICAgICAgICAgIHRpdGxlID0gImBlbnZgIHZhbHVlcyBtdXN0IGJlIHN0cmluZ3MiCiAgICAgICAgICAgIG1lc3NhZ2UgPSBmImBlbnZgIHZhbHVlcyBtdXN0IGJlIHN0cmluZ3MsIGJ1dCB2YWx1ZSBvZiB7a30gaGFzIHR5cGUge3R5cGUodil9IgogICAgICAgICAgICBwcmludChmIjo6ZXJyb3IgdGl0bGU9e3RpdGxlfTo6e21lc3NhZ2V9IikKICAgICAgICAgICAgZXhpdCgxKQoKICAgICAgICB2ID0gdi5zcGxpdCgiXG4iKQoKICAgICAgICB3aXRoIG9wZW4oR0lUSFVCX0VOViwgImEiKSBhcyBmOgogICAgICAgICAgICBpZiBsZW4odikgPT0gMToKICAgICAgICAgICAgICAgIGYud3JpdGUoZiJ7a309e3ZbMF19XG4iKQogICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgZm9yIGxpbmUgaW4gdjoKICAgICAgICAgICAgICAgICAgICBhc3NlcnQgbGluZS5zdHJpcCgpICE9IERFTElNSVRFUgogICAgICAgICAgICAgICAgZi53cml0ZShmIntrfTw8e0RFTElNSVRFUn1cbiIpCiAgICAgICAgICAgICAgICBmb3IgbGluZSBpbiB2OgogICAgICAgICAgICAgICAgICAgIGYud3JpdGUoZiJ7bGluZX1cbiIpCiAgICAgICAgICAgICAgICBmLndyaXRlKGYie0RFTElNSVRFUn1cbiIpCgogICAgICAgIHByaW50KGYie2t9IHdyaXR0ZW4gdG8gR0lUSFVCX0VOViIpCgoKaWYgX19uYW1lX18gPT0gIl9fbWFpbl9fIjoKICAgIHNldF9lbnYoc3lzLmFyZ3ZbMV0pCg== + MATRIX_SETENV: ${{ matrix.setenv }} + + - name: Setup headless display + if: ${{ matrix.display == 'true' }} + uses: pyvista/setup-headless-display-action@5bc8de3bc71fcda7a96439571287a554901541a0 # v4.3 + + - run: uv pip install tox ${{ matrix.toxdeps }} # zizmor: ignore[template-injection] + + - run: tox -e ${{ matrix.toxenv }} ${{ matrix.toxargs }} -- ${{ matrix.pytest_flag }} ${{ matrix.posargs }} # zizmor: ignore[template-injection] + + - if: ${{ (success() || failure()) && matrix.artifact-path != '' }} + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + with: + name: ${{ matrix.artifact-name }} + path: ${{ matrix.artifact-path }} + archive: ${{ matrix.artifact-archive }} + if-no-files-found: ${{ matrix.artifact-if-no-files-found }} + include-hidden-files: ${{ matrix.artifact-include-hidden-files }} + + - if: ${{ (success() || failure()) && matrix.pytest-results-summary == 'true' && matrix.pytest == 'true' }} + uses: test-summary/action@31493c76ec9e7aa675f1585d3ed6f1da69269a86 # v2.4 + with: + paths: "**/results.xml" + + - name: Upload to Codecov + # Even if tox fails, upload coverage + if: ${{ (success() || failure()) && contains(matrix.coverage, 'codecov') && matrix.pytest == 'true' }} + uses: codecov/codecov-action@1af58845a975a7985b0beb0cbe6fbbb71a41dbad # v5.5.3 + with: + token: ${{ secrets.CODECOV_TOKEN }} # zizmor: ignore[secrets-outside-env] + + - name: Upload coverage data to GitHub + id: upload-coverage-gh + if: ${{ (success() || failure()) && contains(matrix.coverage, 'github') && matrix.pytest == 'true' }} + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + with: + name: coverage-data-${{ github.run_id }}-${{ runner.os }}-${{ runner.arch }}-${{ matrix.toxenv }} + path: .coverage.* + if-no-files-found: error + include-hidden-files: true + outputs: + coverage-gh: ${{ steps.upload-coverage-gh.outputs.artifact-id }} + + report_overall_test_coverage: + needs: [ tox ] + if: needs.tox.outputs.coverage-gh + name: report overall test coverage + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + lfs: true + submodules: ${{ inputs.submodules }} + ref: ${{ inputs.checkout_ref }} + persist-credentials: false + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + - name: download coverage reports + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + pattern: coverage-data-${{ github.run_id }}-* + merge-multiple: true + - uses: astral-sh/setup-uv@6ee6290f1cbc4156c0bdd66691b2c144ef8df19a # v7.4.0 + with: + ignore-empty-workdir: "true" + - name: generate coverage report + run: | + uvx coverage combine + uvx coverage report -i -m --format=markdown >> $GITHUB_STEP_SUMMARY + - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + with: + name: coverage-report-${{ hashFiles('.coverage') }} + path: .coverage + if-no-files-found: error + include-hidden-files: true