diff --git a/.github/RELEASE-core.md b/.github/RELEASE-core.md index dcb81ab8bc..01e182c76e 100644 --- a/.github/RELEASE-core.md +++ b/.github/RELEASE-core.md @@ -123,41 +123,21 @@ Pushing the tag triggers a CI run automatically. Monitor it in the ## Upload wheels to PyPI -This is a two-stage process: first publish to TestPyPI, verify, then -publish to PyPI. - -### Stage 1: TestPyPI +This is a single `CI: Release` workflow run with two sequential stages: +publish to TestPyPI, then publish the same wheel set to PyPI. 1. Go to **Actions > CI: Release** and run the workflow with: - **Component**: `cuda-core` - **The release git tag**: `cuda-core-v0.6.0` - - **The GHA run ID that generated validated artifacts**: This is the - run ID of the successful tag-triggered CI run from the previous step. - You can find it in the URL when viewing the run in the Actions tab - (e.g. `https://github.com/NVIDIA/cuda-python/actions/runs/123456789` - — the run ID is `123456789`). - - **Which wheel index to publish to**: `testpypi` + The workflow automatically looks up the successful tag-triggered CI run for the selected release tag. -2. Wait for the workflow to complete. - -3. Verify the TestPyPI upload by installing and running tests from a - checked-out copy of the repository: - - ```bash - pip install -i https://test.pypi.org/simple/ \ - --extra-index-url https://pypi.org/simple/ \ - cuda-core==0.6.0 - cd cuda_core/tests && pytest - ``` - -### Stage 2: PyPI - -Once TestPyPI verification passes, rerun the same workflow with: -- **Which wheel index to publish to**: `pypi` +2. Wait for the workflow to complete. It will: + - publish the selected wheels to TestPyPI + - publish the same wheel set to PyPI -After completion, verify: +3. After completion, verify the final PyPI upload: ```bash pip install cuda-core==0.6.0 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ce88820d51..97d58d8ae5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,7 +4,9 @@ name: "CI: Release" -# Manually-triggered release workflow. Creates a release draft if one doesn't exist for the given tag, or uses existing draft. +# Manually-triggered release workflow. Creates a release draft if one doesn't exist +# for the given tag, or uses an existing draft, then publishes the selected wheels +# to TestPyPI followed by PyPI. on: workflow_dispatch: @@ -28,13 +30,6 @@ on: required: false type: string default: "" - wheel-dst: - description: "Which wheel index to publish to?" - required: true - type: choice - options: - - testpypi - - pypi defaults: run: @@ -60,7 +55,7 @@ jobs: echo "Auto-detecting successful tag-triggered run ID for tag: ${{ inputs.git-tag }}" RUN_ID=$(./ci/tools/lookup-run-id "${{ inputs.git-tag }}" "${{ github.repository }}") echo "Auto-detected run ID: $RUN_ID" - echo "run-id=$RUN_ID" >> $GITHUB_OUTPUT + echo "run-id=$RUN_ID" >> "$GITHUB_OUTPUT" check-tag: runs-on: ubuntu-latest @@ -74,17 +69,11 @@ jobs: env: GH_TOKEN: ${{ github.token }} run: | - tags= - for i in $(gh release list -R ${{ github.repository }} --json tagName --jq '.[]| .tagName'); do - tags+=( $i ) - done - is_draft= - for i in $(gh release list -R ${{ github.repository }} --json isDraft --jq '.[]| .isDraft'); do - is_draft+=( $i ) - done + mapfile -t tags < <(gh release list -R "${{ github.repository }}" --json tagName --jq '.[] | .tagName') + mapfile -t is_draft < <(gh release list -R "${{ github.repository }}" --json isDraft --jq '.[] | .isDraft') found=0 - for idx in ${!tags[@]}; do + for idx in "${!tags[@]}"; do if [[ "${tags[$idx]}" == "${{ inputs.git-tag }}" ]]; then echo "found existing release for ${{ inputs.git-tag }}" found=1 @@ -134,16 +123,16 @@ jobs: run-id: ${{ needs.determine-run-id.outputs.run-id }} component: ${{ inputs.component }} - publish-wheels: - name: Publish wheels + publish-testpypi: + name: Publish wheels to TestPyPI runs-on: ubuntu-latest needs: - check-tag - determine-run-id - doc environment: - name: ${{ inputs.wheel-dst }} - url: https://${{ (inputs.wheel-dst == 'testpypi' && 'test.') || '' }}pypi.org/p/${{ inputs.component }}/ + name: testpypi + url: https://test.pypi.org/${{ inputs.component != 'all' && format('p/{0}/', inputs.component) || '' }} permissions: id-token: write steps: @@ -160,14 +149,37 @@ jobs: run: | ./ci/tools/validate-release-wheels "${{ inputs.git-tag }}" "${{ inputs.component }}" "dist" - - name: Publish package distributions to PyPI - if: ${{ inputs.wheel-dst == 'pypi' }} - uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0 - - name: Publish package distributions to TestPyPI - if: ${{ inputs.wheel-dst == 'testpypi' }} uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0 with: repository-url: https://test.pypi.org/legacy/ + publish-pypi: + name: Publish wheels to PyPI + runs-on: ubuntu-latest + needs: + - determine-run-id + - publish-testpypi + environment: + name: pypi + url: https://pypi.org/${{ inputs.component != 'all' && format('p/{0}/', inputs.component) || '' }} + permissions: + id-token: write + steps: + - name: Checkout Source + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Download component wheels + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + ./ci/tools/download-wheels "${{ needs.determine-run-id.outputs.run-id }}" "${{ inputs.component }}" "${{ github.repository }}" "dist" + + - name: Validate wheel versions for release tag + run: | + ./ci/tools/validate-release-wheels "${{ inputs.git-tag }}" "${{ inputs.component }}" "dist" + + - name: Publish package distributions to PyPI + uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0 + # TODO: add another job to make the release leave the draft state?