name: Dist
on:
  workflow_dispatch:
  schedule:
    # gemini said this is 6:00 china time
    - cron: "0 22 * * *"
  pull_request:
    types:
      - opened
      - synchronize
      - reopened
      - ready_for_review
    paths:
      - setup.py
      - setup.cfg
      - pyproject.toml
      - MANIFEST.in
      - CMakeLists.txt
      - version_provider.py
      - .github/workflows/dist.yml
  release:
    types:
      - published

permissions:
  contents: read

concurrency:
  group: "${{ github.workflow }}-${{ github.ref }}"
  cancel-in-progress: true

env:
  PYTHONDEVMODE: "1"
  PYTHONUNBUFFERED: "1"
  COLUMNS: "100"
  FORCE_COLOR: "1"
  CLICOLOR_FORCE: "1"

jobs:
  build-sdist:
    name: Build SDist
    if: |
      github.repository_owner == 'tile-ai' &&
      (github.event_name != 'pull_request' || !github.event.pull_request.draft)
    runs-on: macos-latest
    timeout-minutes: 30
    env:
      # `NO_VERSION_LABEL=ON` disables embedding the toolchain / git commit hash in version metadata.
      # Otherwise, the version of the SDist has a git hash suffix (e.g., 0.1.0+gitabcdef12),
      # but the package built from the SDist has no way to get the git hash (it is not a git repo),
      # leading to inconsistent versions between SDist and built packages (+gitabcdef12 vs. +gitunknown).
      NO_VERSION_LABEL: 'ON'

    steps:
      - name: Checkout repository
        uses: actions/checkout@v6
        with:
          fetch-depth: 1
          submodules: recursive

      - name: Setup Python and uv with caching
        id: setup-uv
        uses: astral-sh/setup-uv@v7
        with:
          python-version: "3.12"
          activate-environment: true

      - name: Build SDist
        run: |
          uv run --no-project --with=build -m -- build --sdist --outdir=dist

      - name: Setup ccache
        uses: hendrikmuhs/ccache-action@v1
        with:
          create-symlink: true
          evict-old-files: "7d"
          append-timestamp: false
          key: sdist-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/*.cc') }}
          restore-keys: |
            sdist-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/*.cc') }}
            sdist-${{ runner.os }}-${{ runner.arch }}
            ${{ runner.os }}-${{ runner.arch }}

      - name: Test SDist buildable
        run: |
          TEMP_DIR="$(mktemp -d -t tilelang-sdist-test)"
          cp -r dist "${TEMP_DIR}/dist"
          cd "${TEMP_DIR}"
          uv pip install -v dist/*.tar.gz
          python3 -c "import tilelang; print(tilelang.__version__)"

      - name: Upload SDist
        # Not PR to save artifact storage, as SDist is only needed for releases.
        if: github.event_name != 'pull_request' || contains(github.event.pull_request.title, '[Release]')
        uses: actions/upload-artifact@v5
        with:
          name: sdist
          path: dist/*.tar.gz
          if-no-files-found: error

  build-wheels:
    name: Build wheels for Python ${{ matrix.python-version }} on ${{ matrix.target.runner }} with ${{ matrix.target.toolkit }}
    if: |
      github.repository_owner == 'tile-ai' &&
      (github.event_name != 'pull_request' || !github.event.pull_request.draft)
    strategy:
      matrix:
        target:
          - { runner: ubuntu-latest, toolkit: "CUDA-12.1" }
          - { runner: ubuntu-24.04-arm, toolkit: "CUDA-12.8" }
          - { runner: macos-latest, toolkit: "Metal" }
        python-version:
          # Wheels are built with Python 3.8 Limited API, they should work with all Python >= 3.8.
          # Only build wheels against Python 3.8 Limited API to save CI resources.
          # FIXME: Here we use Python 3.9 because our dependency `apache-tvm-ffi` claims to support
          #        Python 3.8 but it depends on a version of `ml-dtypes` that requires Python >= 3.9.
          - "3.9"
      fail-fast: false
    timeout-minutes: 120
    runs-on: ${{ matrix.target.runner }}
    env:
      NO_VERSION_LABEL: ${{ github.event_name == 'release' && 'OFF' || 'ON' }}

    steps:
      - name: Checkout repository
        uses: actions/checkout@v6
        with:
          fetch-depth: 1
          submodules: recursive

      - name: Setup ccache
        uses: hendrikmuhs/ccache-action@v1
        with:
          create-symlink: true
          evict-old-files: "7d"
          append-timestamp: false
          key: wheel-${{ runner.os }}-${{ runner.arch }}-${{ matrix.target.toolkit }}-${{ hashFiles('**/*.cc') }}
          restore-keys: |
            wheel-${{ runner.os }}-${{ runner.arch }}-${{ matrix.target.toolkit }}-${{ hashFiles('**/*.cc') }}
            wheel-${{ runner.os }}-${{ runner.arch }}-${{ matrix.target.toolkit }}
            wheel-${{ runner.os }}-${{ runner.arch }}
            ${{ runner.os }}-${{ runner.arch }}-${{ matrix.target.toolkit }}
            ${{ runner.os }}-${{ runner.arch }}

      - name: Set CIBW_BUILD
        run: |
          PYTHON_VERSION="${{ matrix.python-version }}"
          PYTHON_VERSION_MAJMIN="$(echo "${PYTHON_VERSION}" | cut -d '.' -f-2)"
          PYTHON_VERSION_MAJMIN_NODOT="${PYTHON_VERSION_MAJMIN//./}"
          echo "CIBW_BUILD=cp${PYTHON_VERSION_MAJMIN_NODOT}-*" | tee -a "${GITHUB_ENV}"

          if [[ "${{ matrix.target.toolkit }}" == *"CUDA"* ]]; then
            CUDA_VERSION="${{ matrix.target.toolkit }}"
            CUDA_VERSION="${CUDA_VERSION#CUDA-}"
            echo "CUDA_VERSION=${CUDA_VERSION}" | tee -a "${GITHUB_ENV}"
          fi

          if [[ "${{ runner.os }}" == "Linux" ]]; then
            HOST_CCACHE_DIR="$(ccache --get-config cache_dir)"
            echo "CIBW_BEFORE_BUILD_LINUX=yum install -y ccache && ccache -o cache_dir=/host${HOST_CCACHE_DIR}" | tee -a "${GITHUB_ENV}"
          fi

      - name: Build wheels
        uses: pypa/cibuildwheel@v3.3
        with:
          package-dir: .
          output-dir: wheelhouse
          config-file: "{package}/pyproject.toml"

      - name: Upload wheels
        # Not PR to save artifact storage, as wheels are only needed for releases.
        if: github.event_name != 'pull_request' || contains(github.event.pull_request.title, '[Release]')
        uses: actions/upload-artifact@v5
        with:
          name: wheels-${{ matrix.python-version }}-${{ runner.os }}-${{ runner.arch }}-${{ matrix.target.toolkit }}
          path: wheelhouse/*.whl
          if-no-files-found: error

  list-artifacts:
    name: List artifacts
    # Not PR to save artifact storage, as artifacts are only needed for releases.
    if: github.event_name != 'pull_request' || contains(github.event.pull_request.title, '[Release]')
    runs-on: ubuntu-latest
    needs: [build-sdist, build-wheels]
    timeout-minutes: 15
    steps:
      - name: Download built SDist
        uses: actions/download-artifact@v6
        with:
          # unpacks default artifact into dist/
          # if `name: artifact` is omitted, the action will create extra parent dir
          name: sdist
          path: dist

      - name: Download built wheels
        uses: actions/download-artifact@v6
        with:
          pattern: wheels-*
          path: dist
          merge-multiple: true

      - name: List distributions
        run: ls -lh dist/*

      - name: Upload artifacts
        uses: actions/upload-artifact@v5
        with:
          name: artifacts
          path: dist/*
          if-no-files-found: error
