diff --git a/.github/workflows/publish-pypi.yaml b/.github/workflows/publish-pypi.yaml new file mode 100644 index 0000000..5989f29 --- /dev/null +++ b/.github/workflows/publish-pypi.yaml @@ -0,0 +1,125 @@ +name: Publish PyPI Wheels + +on: + workflow_dispatch: + inputs: + tag: + description: "Git tag to publish (empty = build current commit, artifact only)" + required: false + default: "" + type: string + +run-name: ${{ inputs.tag != '' && format('pypi/{0}', inputs.tag) || format('artifacts-only/{0}', github.sha) }} + +jobs: + resolve-target: + name: Resolve build target + runs-on: ubuntu-latest + outputs: + ref: ${{ steps.target.outputs.ref }} + publish: ${{ steps.target.outputs.publish }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Resolve ref and publish mode + id: target + shell: bash + run: | + raw_tag="${{ inputs.tag }}" + if [[ -z "$raw_tag" ]]; then + echo "ref=${GITHUB_SHA}" >> "$GITHUB_OUTPUT" + echo "publish=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + + if [[ "$raw_tag" == refs/tags/* ]]; then + tag_ref="$raw_tag" + else + tag_ref="refs/tags/$raw_tag" + fi + + git fetch --force --tags + git rev-parse --verify "$tag_ref" + echo "ref=$tag_ref" >> "$GITHUB_OUTPUT" + echo "publish=true" >> "$GITHUB_OUTPUT" + + build-and-test: + name: ${{ matrix.label }} + runs-on: ${{ matrix.os }} + needs: resolve-target + strategy: + fail-fast: false + matrix: + include: + - label: Linux (x86_64) + os: ubuntu-latest + archs: x86_64 + artifact: wheels-linux-x86_64 + - label: Linux (aarch64) + os: ubuntu-24.04-arm + archs: aarch64 + artifact: wheels-linux-aarch64 + - label: Windows (AMD64) + os: windows-latest + archs: AMD64 + artifact: wheels-windows + - label: macOS (arm64) + os: macos-14 + archs: arm64 + artifact: wheels-macos + + steps: + - name: Checkout tag + uses: actions/checkout@v4 + with: + ref: ${{ needs.resolve-target.outputs.ref }} + fetch-depth: 0 + submodules: true + + - name: Build wheels and run wheel tests + uses: pypa/cibuildwheel@v2.22.0 + env: + CIBW_BUILD: cp310-* cp311-* cp312-* cp313-* + CIBW_SKIP: "*-musllinux_*" + CIBW_TEST_REQUIRES: pytest scipy==1.15.0 # 1.15.0 have wheels for Python 3.10 + CIBW_TEST_COMMAND: pytest -q {project}/tests + CIBW_PROJECT_REQUIRES_PYTHON: ">=3.10,<3.14" + CIBW_ARCHS: ${{ matrix.archs }} + + - name: Upload wheel artifacts + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.artifact }} + path: wheelhouse/*.whl + if-no-files-found: error + + publish-to-pypi: + name: Publish to PyPI + runs-on: ubuntu-latest + needs: + - resolve-target + - build-and-test + if: ${{ needs.resolve-target.outputs.publish == 'true' }} + permissions: + id-token: write + + steps: + - name: Download all wheel artifacts + uses: actions/download-artifact@v4 + with: + pattern: wheels-* + merge-multiple: true + path: dist + + - name: Verify wheel files + run: ls -lh dist/*.whl + + - name: Upload to PyPI (Trusted Publisher) + uses: pypa/gh-action-pypi-publish@release/v1 + with: + repository-url: https://upload.pypi.org/legacy/ + packages-dir: dist + verbose: true \ No newline at end of file diff --git a/.gitignore b/.gitignore index 4d3b0cb..4d7d6a7 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ sample.* # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] +.mypy_cache # C extensions *.so diff --git a/pyproject.toml b/pyproject.toml index 9de1588..e3bbda7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,6 +29,9 @@ classifiers = [ "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "License :: OSI Approved :: MIT License", "Topic :: Scientific/Engineering", "Topic :: Software Development",