| name: Release to PyPI |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| on: |
| push: |
| tags: |
| - 'v*.*.*' |
| workflow_dispatch: |
| inputs: |
| testpypi_only: |
| description: "Publish to TestPyPI only (skip PyPI + GitHub release)" |
| type: boolean |
| default: true |
|
|
| env: |
| FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" |
|
|
| jobs: |
| |
| |
| |
| |
| build: |
| name: Build sdist + wheel |
| runs-on: ubuntu-latest |
| steps: |
| - uses: actions/checkout@v4 |
| with: |
| fetch-depth: 0 |
|
|
| - uses: actions/setup-python@v5 |
| with: |
| python-version: "3.12" |
|
|
| - name: Install build tooling |
| run: python -m pip install --upgrade pip build |
|
|
| |
| |
| |
| - name: Verify pyproject version matches tag |
| if: github.event_name == 'push' |
| run: | |
| tag_version="${GITHUB_REF_NAME#v}" |
| pkg_version=$(python -c " |
| import tomllib, pathlib |
| print(tomllib.loads(pathlib.Path('pyproject.toml').read_text())['project']['version']) |
| ") |
| if [ "${tag_version}" != "${pkg_version}" ]; then |
| echo "::error::Tag v${tag_version} does not match pyproject.toml version ${pkg_version}." |
| echo "Bump pyproject.toml or retag before pushing." |
| exit 1 |
| fi |
| echo "Version check OK: ${pkg_version}" |
| |
| - name: Build distributions |
| run: python -m build |
|
|
| - name: List built artifacts |
| run: ls -la dist/ |
|
|
| - uses: actions/upload-artifact@v4 |
| with: |
| name: dist |
| path: dist/ |
| if-no-files-found: error |
|
|
| |
| |
| |
| publish-testpypi: |
| name: Publish to TestPyPI |
| needs: build |
| runs-on: ubuntu-latest |
| environment: |
| name: testpypi |
| url: https://test.pypi.org/project/qcal-copilot/ |
| permissions: |
| id-token: write |
| steps: |
| - uses: actions/download-artifact@v4 |
| with: |
| name: dist |
| path: dist/ |
|
|
| - uses: pypa/gh-action-pypi-publish@release/v1 |
| with: |
| repository-url: https://test.pypi.org/legacy/ |
| |
| |
| skip-existing: true |
|
|
| |
| |
| |
| |
| smoke-test-testpypi: |
| name: Smoke-test TestPyPI install |
| needs: publish-testpypi |
| runs-on: ubuntu-latest |
| steps: |
| - uses: actions/setup-python@v5 |
| with: |
| python-version: "3.12" |
|
|
| - name: Install from TestPyPI |
| run: | |
| # TestPyPI doesn't mirror transitive deps — fall back to PyPI for those. |
| # Retry a few times because TestPyPI's CDN takes 30-60s to propagate |
| # a fresh upload before the new version becomes resolvable. |
| for attempt in 1 2 3 4 5; do |
| if python -m pip install \ |
| --index-url https://test.pypi.org/simple/ \ |
| --extra-index-url https://pypi.org/simple/ \ |
| qcal-copilot; then |
| exit 0 |
| fi |
| echo "Attempt ${attempt} failed, sleeping before retry..." |
| sleep 15 |
| done |
| echo "::error::pip install from TestPyPI failed after 5 attempts." |
| exit 1 |
| |
| - name: Verify CLI + imports |
| run: | |
| qcal version |
| python -c " |
| import qcal |
| from qcal import analyzer, codegen, data, decoder, fit, simulator, config, cli |
| assert qcal.__version__, 'missing __version__' |
| print('smoke test OK, version =', qcal.__version__) |
| " |
| |
| |
| |
| |
| publish-pypi: |
| name: Publish to PyPI |
| needs: smoke-test-testpypi |
| if: github.event_name == 'push' || github.event.inputs.testpypi_only != 'true' |
| runs-on: ubuntu-latest |
| environment: |
| name: pypi |
| url: https://pypi.org/project/qcal-copilot/ |
| permissions: |
| id-token: write |
| steps: |
| - uses: actions/download-artifact@v4 |
| with: |
| name: dist |
| path: dist/ |
|
|
| - uses: pypa/gh-action-pypi-publish@release/v1 |
|
|
| |
| |
| |
| |
| github-release: |
| name: Create GitHub Release |
| needs: publish-pypi |
| if: github.event_name == 'push' |
| runs-on: ubuntu-latest |
| permissions: |
| contents: write |
| steps: |
| - uses: actions/download-artifact@v4 |
| with: |
| name: dist |
| path: dist/ |
|
|
| - uses: softprops/action-gh-release@v2 |
| with: |
| files: dist/* |
| generate_release_notes: true |
|
|