name: Build and Publish to PyPI

on:
    # Trigger the workflow on push or pull request,
    # for the dev and any release branches
    push:
        branches:
            - dev
            - "release-**"
        tags:
            - "v*"
    pull_request:
        branches:
            - dev
            - "release-**"
    # Trigger the workflow for when a release is created
    release:
        types:
            - published

env:
    LC_ALL: en_US.UTF-8

defaults:
    run:
        shell: bash

permissions:
    contents: read

jobs:
    # Create release artifacts
    # - build source dist (tar ball) and wheel
    # - upload artifacts to GHA
    build-artifacts:
        name: Build artifacts
        runs-on: ubuntu-latest
        steps:
            - name: "Harden Runner"
              uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0
              with:
                disable-sudo-and-containers: false
                egress-policy: block
                allowed-endpoints: >
                  api.github.com:443
                  github.com:443
                  registry-1.docker.io:443
                  production.cloudflare.docker.com:443
                  auth.docker.io:443
                  azure.archive.ubuntu.com:80
                  pypi.org:443
                  files.pythonhosted.org:443
                  codeload.github.com:443
                  objects.githubusercontent.com:443
                  releases.githubusercontent.com:443

            - name: Checkout code
              uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
              with:
                # for setuptools-scm
                fetch-depth: 0

            - name: Free disk space
              uses: ./.github/actions/free-disk-space

            - name: Setup Python 3.13
              uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
              with:
                python-version: "3.13"

            - name: Install build tooling
              run: |
                python -m pip install --upgrade pip
                pip install build cibuildwheel

            - name: Clean up release artifacts
              run: |
                rm -rf dist/

            - name: Build source distribution (no CUDA)
              run: |
                NO_CUDA_EXT=1 python -m build --sdist

            - name: Build CUDA wheels with cibuildwheel
              # Configuration is set in pyproject.toml
              # this will build lmcache with the latest version of torch
              # building from source will be required if a serving engine uses
              # an earlier non-ABI compatible version of torch
              run: |
                python -m cibuildwheel --output-dir dist

            - name: Upload release artifacts to GHA
              uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
              with:
                name: release-artifacts
                path: dist/

    # Run tests and code quality checks before publishing
    test:
        name: Run tests
        uses: ./.github/workflows/test.yml

    code-quality:
        name: Run code quality checks
        uses: ./.github/workflows/code_quality_checks.yml

    # Push to Test PyPI when:
    # - a new GitHub release is published
    # - a PR is merged into dev branch (push only trigger)
    publish-test-pypi:
        name: Publish packages to test.pypi.org
        if: |
            github.repository_owner == 'LMCache' && (
                github.event.action == 'published' ||
                (github.event_name == 'push' && github.ref == 'refs/heads/dev')
            )
        permissions:
            contents: read
            # see https://docs.pypi.org/trusted-publishers/
            id-token: write
        runs-on: ubuntu-latest
        needs: [build-artifacts, test, code-quality]

        steps:
            - name: Harden Runner
              uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0
              with:
                disable-sudo-and-containers: false
                egress-policy: block
                allowed-endpoints: >
                  ghcr.io:443
                  pkg-containers.githubusercontent.com:443
                  test.pypi.org:443
                  tuf-repo-cdn.sigstore.dev:443
                  fulcio.sigstore.dev:443
                  rekor.sigstore.dev:443

            - name: Fetch release artifacts
              uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
              with:
                  name: release-artifacts
                  path: dist

            - name: Upload to Test PyPI
              uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0
              with:
                  repository-url: https://test.pypi.org/legacy/
                  verbose: true

    # Push to PyPI (production) when:
    # - a new GitHub release is created
    publish-pypi:
        name: Publish release to pypi.org
        if: |
            github.repository_owner == 'LMCache' && github.event.action == 'published'
        permissions:
            # see https://docs.pypi.org/trusted-publishers/
            id-token: write
            # allow gh release upload
            contents: write

        runs-on: ubuntu-latest
        needs: [build-artifacts, test, code-quality]

        steps:
            - name: Harden Runner
              uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0
              with:
                  disable-sudo-and-containers: false
                  egress-policy: block
                  allowed-endpoints: >
                    api.github.com:443
                    uploads.github.com:443
                    ghcr.io:443
                    pkg-containers.githubusercontent.com:443
                    upload.pypi.org:443
                    tuf-repo-cdn.sigstore.dev:443
                    fulcio.sigstore.dev:443
                    rekor.sigstore.dev:443

            - name: Fetch release artifacts
              uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
              with:
                  name: release-artifacts
                  path: dist

            - name: Upload release artifacts to GitHub release
              env:
                  GITHUB_TOKEN: ${{ github.token }}
              run: >-
                  gh release upload '${{ github.ref_name }}' dist/* --repo '${{ github.repository }}'

            - name: Upload to PyPI
              uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0
              with:
                verbose: true

    # Build container image and push to DockerHub when:
    # - a new GitHub release is created
    publish-image:
        name: Publish image to DockerHub
        if: |
            github.repository_owner == 'LMCache' && github.event.action == 'published'

        runs-on: ubuntu-latest
        needs: publish-pypi

        steps:
            - name: "Harden Runner"
              uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0
              with:
                disable-sudo-and-containers: false
                egress-policy: block
                allowed-endpoints: >
                  github.com:443
                  registry-1.docker.io:443
                  production.cloudflare.docker.com:443
                  auth.docker.io:443
                  azure.archive.ubuntu.com:80
                  archive.ubuntu.com:80
                  security.ubuntu.com:80
                  astral.sh:443
                  objects.githubusercontent.com:443
                  pypi.org:443
                  files.pythonhosted.org:443
                  release-assets.githubusercontent.com:443
                  wrapdb.mesonbuild.com:443
                  nvcr.io:443
                  layers.nvcr.io:443

            - name: Login to DockerHub
              uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
              with:
                username: ${{ vars.DOCKERHUB_USERNAME }}
                password: ${{ secrets.DOCKERHUB_TOKEN }}

            - name: Set up Docker Buildx
              uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1

            - name: Checkout code
              uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
              with:
                # for setuptools-scm
                fetch-depth: 0

            - name: Free disk space
              uses: ./.github/actions/free-disk-space

            - name: Get the latest tag (corresponds to release cut in publish-pypi job)
              run: |
                echo "LATEST_TAG=$(git describe --tags --abbrev=0)" >> "$GITHUB_ENV"

            - name: Build lmcache/vllm-openai container image with latest releases
              run: |
                docker build \
                --build-arg CUDA_VERSION=12.8 --build-arg UBUNTU_VERSION=24.04 \
                --target image-release \
                --tag lmcache/vllm-openai:latest --tag lmcache/vllm-openai:${{ env.LATEST_TAG }} \
                --file docker/Dockerfile .

            - name: Push lmcache/vllm-openai container image to DockerHub
              run: |
                docker push lmcache/vllm-openai:latest
                docker push lmcache/vllm-openai:${{ env.LATEST_TAG }}
            
            - name: Clean up Docker layers and cache
              run: |
                docker system prune -f --all
                docker builder prune -af

            - name: Build lmcache/vllm-openai:lightweight
              run: | 
                docker build \
                --tag lmcache/vllm-openai:lightweight --tag lmcache/vllm-openai:${{ env.LATEST_TAG }}-lightweight \
                --file docker/Dockerfile.lightweight .
            
            - name: Push lmcache/vllm-openai:lightweight image to DockerHub
              run: |
                docker push lmcache/vllm-openai:lightweight
                docker push lmcache/vllm-openai:${{ env.LATEST_TAG }}-lightweight

            - name: Clean up Docker layers and cache
              run: |
                docker system prune -f --all
                docker builder prune -af

            - name: Build lmcache/standalone container image
              run: |
                docker build \
                --build-arg CUDA_VERSION=12.8 --build-arg UBUNTU_VERSION=24.04 \
                --target lmcache-final \
                --tag lmcache/standalone:latest --tag lmcache/standalone:${{ env.LATEST_TAG }} \
                --file docker/Dockerfile.standalone .
            
            - name: Push lmcache/standalone container image to DockerHub
              run: |
                docker push lmcache/standalone:latest
                docker push lmcache/standalone:${{ env.LATEST_TAG }}