# Ultralytics ๐Ÿš€ AGPL-3.0 License - https://ultralytics.com/license # Publish pip package to PyPI https://pypi.org/project/ultralytics-actions/ name: Publish to PyPI on: push: branches: [main] workflow_dispatch: inputs: pypi: type: boolean description: Publish to PyPI concurrency: group: ${{ github.workflow }} queue: max jobs: deploy-actions: if: github.repository == 'ultralytics/actions' && github.event_name == 'push' runs-on: ubuntu-latest permissions: {} steps: - name: Redeploy Cloud Run actions service env: GH_TOKEN: ${{ secrets._GITHUB_TOKEN }} run: gh workflow run deploy_cloud_run.yml --repo ultralytics/assistant --ref main -f actions=true check: if: github.repository == 'ultralytics/actions' && github.actor == 'glenn-jocher' && github.ref == 'refs/heads/main' runs-on: ubuntu-latest permissions: contents: write outputs: increment: ${{ steps.check_pypi.outputs.increment }} current_tag: ${{ steps.check_pypi.outputs.current_tag }} previous_tag: ${{ steps.check_pypi.outputs.previous_tag }} steps: - uses: actions/checkout@v7 - uses: actions/setup-python@v7 with: python-version: "3.x" - uses: ultralytics/actions/setup-uv@main - run: uv pip install --system --no-cache ultralytics-actions - id: check_pypi shell: python env: PYPI_DISPATCH: ${{ github.event.inputs.pypi }} run: | import os from actions.utils import check_pypi_version local_version, online_version, publish = check_pypi_version() publish = publish or os.environ.get("PYPI_DISPATCH") == "true" # manual recovery re-run os.system(f'echo "increment={publish}" >> $GITHUB_OUTPUT') os.system(f'echo "current_tag=v{local_version}" >> $GITHUB_OUTPUT') if online_version != local_version: # empty on recovery re-runs so summarize falls back to the true previous tag os.system(f'echo "previous_tag=v{online_version}" >> $GITHUB_OUTPUT') if publish: print('Ready to publish new version to PyPI โœ….') - name: Tag and Release if: steps.check_pypi.outputs.increment == 'True' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} CURRENT_TAG: ${{ steps.check_pypi.outputs.current_tag }} PREVIOUS_TAG: ${{ steps.check_pypi.outputs.previous_tag }} OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} run: | if [ -z "$(git ls-remote --tags origin "refs/tags/$CURRENT_TAG")" ]; then git config --global user.name "UltralyticsAssistant" git config --global user.email "web@ultralytics.com" git tag -a "$CURRENT_TAG" -m "$(git log -1 --pretty=%B)" git push origin "$CURRENT_TAG" fi if ! gh release view "$CURRENT_TAG" >/dev/null 2>&1; then [ -n "$PREVIOUS_TAG" ] || git fetch --unshallow --tags # summarize resolves the previous tag from git history ultralytics-actions-summarize-release fi uv cache prune --ci build: needs: check if: needs.check.outputs.increment == 'True' runs-on: ubuntu-latest permissions: contents: read steps: - uses: actions/checkout@v7 - uses: actions/setup-python@v7 with: python-version: "3.x" - uses: ultralytics/actions/setup-uv@main - run: uv pip install --system --no-cache build - run: python -m build - uses: actions/upload-artifact@v7 with: name: dist path: dist/ - run: uv cache prune --ci publish: needs: [check, build] if: needs.check.outputs.increment == 'True' runs-on: ubuntu-latest environment: # for GitHub Deployments tab name: Release - PyPI url: https://pypi.org/p/ultralytics-actions permissions: id-token: write # for PyPI trusted publishing steps: - uses: actions/download-artifact@v8 with: name: dist path: dist/ - uses: pypa/gh-action-pypi-publish@release/v1 id: publish continue-on-error: true with: skip-existing: true # tolerate recovery re-runs after partial failures - name: Clean partial attestations # leftover *.publish.attestation files make the retry fail pre-existence checks if: steps.publish.outcome == 'failure' run: | rm -f dist/*.publish.attestation sleep 30 - name: Retry publish # transient sigstore/Rekor or PyPI network failures if: steps.publish.outcome == 'failure' uses: pypa/gh-action-pypi-publish@release/v1 with: skip-existing: true sbom: needs: [check, build, publish] if: needs.check.outputs.increment == 'True' runs-on: ubuntu-latest permissions: contents: write steps: - uses: actions/checkout@v7 - uses: actions/setup-python@v7 with: python-version: "3.x" - uses: ultralytics/actions/setup-uv@main - run: | uv venv sbom-env uv pip install -e . env: VIRTUAL_ENV: sbom-env - uses: anchore/sbom-action@v0 with: format: spdx-json output-file: sbom.spdx.json path: sbom-env - run: gh release upload ${{ needs.check.outputs.current_tag }} sbom.spdx.json --clobber env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} notify: needs: [check, publish, sbom] if: always() && needs.check.outputs.increment == 'True' runs-on: ubuntu-latest permissions: contents: read steps: - name: Get release title id: release env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_REPO: ${{ github.repository }} TAG: ${{ needs.check.outputs.current_tag }} run: | TITLE=$(gh release view "$TAG" --json name -q .name 2>/dev/null | tr -d '\n\r"\\') || TITLE="" TITLE=$(printf '%s' "$TITLE" | sed -E "s@#([0-9]+)@@g") echo "title=$TITLE" >> "$GITHUB_OUTPUT" - name: Notify Success if: needs.publish.result == 'success' && needs.sbom.result == 'success' && github.event_name == 'push' uses: slackapi/slack-github-action@v4.0.0 with: webhook-type: incoming-webhook webhook: ${{ secrets.SLACK_WEBHOOK_URL_LLM }} payload: | text: "*${{ github.workflow }}* โœ… `${{ github.repository }}` ${{ steps.release.outputs.title || needs.check.outputs.current_tag }} ยท " - name: Notify Failure if: needs.publish.result != 'success' || needs.sbom.result != 'success' uses: slackapi/slack-github-action@v4.0.0 with: webhook-type: incoming-webhook webhook: ${{ secrets.SLACK_WEBHOOK_URL_LLM }} payload: | text: "*${{ github.workflow }}* โŒ `${{ github.repository }}` ${{ needs.check.outputs.current_tag }} "