name: release

# A v* tag builds the release assets of that tag and attaches them with one SHA256SUMS to the GitHub release of the tag (created if it
# does not exist yet):
#   - the .deb and .rpm of pg_sift for each PostgreSQL major of PGS in scripts/package.sh (17, 18 and 19) and each architecture of ARCHES there
#     (x86_64 on ubuntu-24.04, aarch64 on the hosted ubuntu-24.04-arm runner), and the .rpm for Amazon Linux 2023 (its own PostgreSQL 17 and 18),
#     smoke-tested in fresh containers of the same architecture (scripts/package.sh); the publish job refuses a release that lacks one of them;
#   - the SQLite extension tarball sift-sqlite-<version>-linux-x86_64.tar.gz (sift.so, LICENSE, NOTICE, README), built on Debian 12 and
#     loaded into the system SQLite of Debian 12, Ubuntu 24.04 and AlmaLinux 10 (scripts/package-sqlite.sh). The version is the tag
#     and must equal sqlite/VERSION: set it before tagging. A tag without sqlite/VERSION (before 1.3.0) has no SQLite asset.
#   - the MariaDB extension tarball sift-mariadb-<version>-linux-x86_64.tar.gz (libsift_udf.so for each supported MariaDB series, the two SQL
#     files, LICENSE, NOTICE, README, BUILD-INFO), each library built in the stock mariadb:<series> image against that server's headers and
#     loaded into a server of that image (scripts/package-mariadb.sh). The version is the tag and must equal mariadb/VERSION. A tag without
#     mariadb/VERSION has no MariaDB asset.
# The text of the release is docs/releases/<tag>.md: write it before tagging (a PostgreSQL part; scripts/release.sh VERSION --notes appends the Packages part and, for the
# SQLite or the MariaDB tarball, the "## SQLite extension" or "## MariaDB extension" part; the publish job refuses a missing file or section).
# workflow_dispatch rebuilds and republishes the assets of an existing tag with the recipes of the selected ref.
# Dry run: a pull request or a push to main builds and tests the assets of its commit and publishes nothing; it builds only the assets whose recipes
# it touches (packaging/, scripts/package*.sh, sqlite/, mariadb/, this file): a push of anything else ends after the first job.

on:
  push:
    tags: ['v*']
    branches: [main]
  pull_request:
    paths:
      - packaging/**
      - scripts/package.sh
      - scripts/package-sqlite.sh
      - scripts/package-mariadb.sh
      - scripts/port-isa-check.sh
      - sqlite/**
      - mariadb/**
      - .github/workflows/release.yml
  workflow_dispatch:
    inputs:
      tag:
        description: Existing tag to package and attach to its release (for example v1.3.0)
        required: true

permissions:
  contents: read

env:
  REF: ${{ inputs.tag || github.ref_type == 'tag' && github.ref_name || 'HEAD' }}
  # a tag or a dispatch publishes; a pull request or a push to a branch only builds
  PUBLISH: ${{ github.ref_type == 'tag' || github.event_name == 'workflow_dispatch' }}

jobs:
  # which assets this run builds: everything for a tag or a dispatch (the SQLite and MariaDB ones only if the tag has the extension), and for a
  # dry run what its files touch (a pull request: the merge commit against its first parent; a push: against the commit before it)
  changes:
    runs-on: ubuntu-24.04
    timeout-minutes: 5
    outputs:
      packages: ${{ steps.what.outputs.packages }}
      matrix: ${{ steps.what.outputs.matrix }}
      sqlite: ${{ steps.what.outputs.sqlite }}
      mariadb: ${{ steps.what.outputs.mariadb }}
    steps:
      - uses: actions/checkout@v6
        with:
          fetch-depth: 0

      - id: what
        env:
          BEFORE: ${{ github.event.before }}
        run: |
          if [ "$PUBLISH" = false ]; then
            git cat-file -e "$BEFORE^{commit}" 2>/dev/null || BEFORE=HEAD^1
            [ "$GITHUB_EVENT_NAME" = pull_request ] && BEFORE=HEAD^1
            files=$(git diff --name-only "$BEFORE" HEAD)
            packages=false sqlite=false mariadb=false
            grep -Eq '^(packaging/[^/]+|packaging/debian/.*|scripts/package\.sh|\.github/workflows/release\.yml)$' <<<"$files" && packages=true
            grep -Eq '^(sqlite/.*|packaging/sqlite/.*|scripts/package-sqlite\.sh|scripts/port-isa-check\.sh|\.github/workflows/release\.yml)$' <<<"$files" && sqlite=true
            grep -Eq '^(mariadb/.*|sqlite/host/.*|packaging/mariadb/.*|scripts/package-mariadb\.sh|scripts/port-isa-check\.sh|\.github/workflows/release\.yml)$' <<<"$files" && mariadb=true
          else
            packages=true sqlite=false mariadb=false
            git cat-file -e "$REF:sqlite/VERSION" 2>/dev/null && sqlite=true
            git cat-file -e "$REF:mariadb/VERSION" 2>/dev/null && mariadb=true
          fi
          {
            echo "packages=$packages"
            echo "matrix=$(scripts/package.sh matrix)"
            echo "sqlite=$sqlite"
            echo "mariadb=$mariadb"
          } >> "$GITHUB_OUTPUT"
          echo "packages=$packages sqlite=$sqlite mariadb=$mariadb"
        shell: bash {0}

  # every major, family and architecture in parallel: each builds its package and installs it in every image of its family
  packages:
    name: packages (${{ matrix.family }} ${{ matrix.pg }} ${{ matrix.arch }})
    needs: changes
    if: needs.changes.outputs.packages == 'true'
    runs-on: ${{ matrix.arch == 'aarch64' && 'ubuntu-24.04-arm' || 'ubuntu-24.04' }}
    timeout-minutes: 60
    strategy:
      fail-fast: false
      matrix:
        # one job for each package of scripts/package.sh: PGS x FAMILIES x ARCHES (the amzn rpm for the majors of AMZN_PGS only)
        include: ${{ fromJSON(needs.changes.outputs.matrix).include }}
    steps:
      - uses: actions/checkout@v6
        with:
          fetch-depth: 0

      - name: Build and test the packages
        run: PGS=${{ matrix.pg }} FAMILIES=${{ matrix.family }} scripts/package.sh all "$REF" dist

      - uses: actions/upload-artifact@v6
        with:
          name: packages-${{ matrix.family }}-${{ matrix.pg }}-${{ matrix.arch }}
          path: dist/*.${{ matrix.ext }}
          if-no-files-found: error

  sqlite:
    needs: changes
    if: needs.changes.outputs.sqlite == 'true'
    runs-on: ubuntu-24.04
    timeout-minutes: 30
    steps:
      - uses: actions/checkout@v6
        with:
          fetch-depth: 0

      # build in a Debian 12 container, load the tarball into the SQLite of Debian 12, Ubuntu 24.04 and AlmaLinux 10
      - name: Build and test the SQLite extension tarball
        run: scripts/package-sqlite.sh all "$REF" dist-sqlite

      - name: Load it into this runner's SQLite
        run: scripts/package-sqlite.sh test dist-sqlite/sift-sqlite-*.tar.gz host

      # only the tarball: the publish job writes the one SHA256SUMS of all the assets
      - uses: actions/upload-artifact@v6
        with:
          name: sift-sqlite
          path: dist-sqlite/sift-sqlite-*.tar.gz
          if-no-files-found: error

  mariadb:
    needs: changes
    if: needs.changes.outputs.mariadb == 'true'
    runs-on: ubuntu-24.04
    timeout-minutes: 60
    steps:
      - uses: actions/checkout@v6
        with:
          fetch-depth: 0

      # one library per MariaDB series, each built in the stock image of the series and loaded into a server of that image
      - name: Build and test the MariaDB extension tarball
        run: scripts/package-mariadb.sh all "$REF" dist-mariadb

      # only the tarball: the publish job writes the one SHA256SUMS of all the assets
      - uses: actions/upload-artifact@v6
        with:
          name: sift-mariadb
          path: dist-mariadb/sift-mariadb-*.tar.gz
          if-no-files-found: error

  publish:
    if: ${{ !cancelled() && (github.ref_type == 'tag' || github.event_name == 'workflow_dispatch') && needs.packages.result == 'success' && contains(fromJSON('["success","skipped"]'), needs.sqlite.result) && contains(fromJSON('["success","skipped"]'), needs.mariadb.result) }}
    needs: [changes, packages, sqlite, mariadb]
    runs-on: ubuntu-24.04
    permissions:
      contents: write
    env:
      GH_TOKEN: ${{ github.token }}
      GH_REPO: ${{ github.repository }}
      TAG: ${{ inputs.tag || github.ref_name }}
    steps:
      - uses: actions/checkout@v6

      - uses: actions/download-artifact@v7
        with:
          pattern: packages-*
          merge-multiple: true
          path: dist

      - uses: actions/download-artifact@v7
        if: needs.sqlite.result == 'success'
        with:
          name: sift-sqlite
          path: dist

      - uses: actions/download-artifact@v7
        if: needs.mariadb.result == 'success'
        with:
          name: sift-mariadb
          path: dist

      - name: Publish the release notes and assets
        run: |
          shopt -s nullglob
          notes=docs/releases/$TAG.md
          [ -f "$notes" ] || { echo "write $notes first (see the notes of a previous release)" >&2; exit 1; }
          sqlite=(dist/sift-sqlite-*.tar.gz)
          if [ ${#sqlite[@]} -gt 0 ] && ! grep -q '^## SQLite extension' "$notes"; then
            echo "$notes has no '## SQLite extension' section (see the notes of a previous release)" >&2; exit 1
          fi
          mariadb=(dist/sift-mariadb-*.tar.gz)
          if [ ${#mariadb[@]} -gt 0 ] && ! grep -q '^## MariaDB extension' "$notes"; then
            echo "$notes has no '## MariaDB extension' section (see the notes of a previous release)" >&2; exit 1
          fi
          scripts/package.sh verify dist
          # one checksum file for every asset (the build jobs' own are not uploaded or are rewritten here)
          (cd dist && rm -f SHA256SUMS && sha256sum -- *.deb *.rpm *.tar.gz > SHA256SUMS && cat SHA256SUMS)
          title="pg_sift ${TAG#v}"
          if gh release view "$TAG" >/dev/null 2>&1; then gh release edit "$TAG" --title "$title" --notes-file "$notes"
          else gh release create "$TAG" --verify-tag --title "$title" --notes-file "$notes"; fi
          gh release upload "$TAG" dist/*.deb dist/*.rpm dist/*.tar.gz dist/SHA256SUMS --clobber
