name: Release

# Builds Gridline installers for macOS (Apple Silicon + Intel), Windows, and
# Linux, then uploads them to a draft GitHub Release.
#
# Trigger: push a version tag from the `prod` branch (production), e.g.
#   git checkout prod && git pull
#   git tag v0.5.0 && git push origin v0.5.0
#
# SIGNING STATUS: builds are UNSIGNED for now (no code-signing certs yet —
# see README "Download a release"). tauri-action automatically signs +
# notarizes when the signing secrets are present, so the moment we add
# APPLE_CERTIFICATE / APPLE_API_KEY / WINDOWS_CERTIFICATE (or Azure Trusted
# Signing) to repo secrets, future builds are signed — no changes to this
# file required.

on:
  push:
    tags:
      - 'v*'

permissions:
  contents: write

jobs:
  publish:
    strategy:
      fail-fast: false
      matrix:
        include:
          - platform: macos-latest      # Apple Silicon (M1/M2/M3+)
            args: --target aarch64-apple-darwin
          - platform: macos-15-intel    # Intel Macs (last Intel runner; retired ~Aug 2027)
            args: --target x86_64-apple-darwin
          - platform: ubuntu-22.04      # Linux x86_64 (.deb / .rpm / .AppImage)
            args: ''
          - platform: windows-latest    # Windows x86_64 (NSIS .exe + .msi)
            args: ''
    runs-on: ${{ matrix.platform }}
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Install Linux dependencies
        if: matrix.platform == 'ubuntu-22.04'
        run: |
          sudo apt-get update
          sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf

      - name: Set up Bun
        uses: oven-sh/setup-bun@v2

      - name: Set up Rust
        uses: dtolnay/rust-toolchain@stable
        with:
          targets: aarch64-apple-darwin, x86_64-apple-darwin

      - name: Cache Rust build artifacts
        uses: swatinem/rust-cache@v2
        with:
          workspaces: './src-tauri -> target'

      - name: Install frontend dependencies
        run: bun install --frozen-lockfile

      - name: Build PostgreSQL client tools (bundled)
        shell: bash
        run: |
          set -euo pipefail
          PG_VER="16.4"
          OUT="${GITHUB_WORKSPACE}/src-tauri/resources/pg_tools"
          mkdir -p "$OUT"
          case "${{ matrix.platform }}" in
            ubuntu-22.04)
              sudo apt-get update -y
              sudo apt-get install -y build-essential libreadline-dev zlib1g-dev flex bison patchelf
              curl -fsSL "https://ftp.postgresql.org/pub/source/v${PG_VER}/postgresql-${PG_VER}.tar.bz2" -o /tmp/pg.tar.bz2
              tar -xf /tmp/pg.tar.bz2 -C /tmp
              cd /tmp/postgresql-${PG_VER}
              ./configure --prefix=/tmp/pgbuild --without-readline --without-icu CFLAGS="-O2"
              # Build the shared prerequisites (catalog headers + libpq/common/
              # port archives) SERIALLY FIRST. Parallel `make -C` runs recurse
              # into src/common concurrently and race `rm -f` / `ar crs` on the
              # same .a — intermittently failing with 'ar: libpgcommon_srv.a:
              # file format not recognized'. Once the archives exist, the tools'
              # -j builds find them up to date and those recursions are no-ops.
              make -C src/backend generated-headers
              make -C src/interfaces/libpq all
              make -j"$(nproc)" -C src/bin/pg_dump all
              make -j"$(nproc)" -C src/bin/psql all
              cp src/bin/pg_dump/pg_dump src/bin/pg_dump/pg_restore "$OUT"/
              cp src/bin/psql/psql "$OUT"/
              # pg_dump links shared libpq (PG16 has no --disable-shared for the
              # client tools); bundle libpq.so alongside and point the loader at
              # the app's resource dir via $ORIGIN rpath.
              cp src/interfaces/libpq/libpq.so.5 "$OUT"/libpq.so.5
              for b in pg_dump pg_restore psql; do
                patchelf --set-rpath '$ORIGIN' "$OUT/$b"
              done
              ;;
            macos-latest|macos-15-intel)
              curl -fsSL "https://ftp.postgresql.org/pub/source/v${PG_VER}/postgresql-${PG_VER}.tar.bz2" -o /tmp/pg.tar.bz2
              tar -xf /tmp/pg.tar.bz2 -C /tmp
              cd /tmp/postgresql-${PG_VER}
              # ac_cv_func_strchrnul=no: Xcode 16.4's SDK marks strchrnul as
              # introduced in macOS 15.4, so configure detects it and PG's
              # snprintf.c calls the system symbol — which fails to compile
              # (-Werror=unguarded-availability-new) and would crash at runtime
              # on macOS < 15.4. Forcing the check off makes PG use its own
              # inline fallback instead.
              ac_cv_func_strchrnul=no ./configure --prefix=/tmp/pgbuild --without-readline --without-icu CFLAGS="-O2"
              # macOS SDKs always declare strchrnul in <string.h> (Xcode 16.4
              # marks it 'introduced in macOS 15.4'), so even with HAVE_STRCHRNUL
              # disabled PG16's static-inline fallback would collide with the
              # header declaration on newer SDKs. Rename PG's fallback so the
              # tools compile on any SDK and never touch the (15.4+ only)
              # system symbol.
              sed -i '' 's/strchrnul/pg_strchrnul/g' src/port/snprintf.c
              # Serial shared-prerequisite build first (see the Linux block: the
              # archives must exist before the -j tool builds recurse into
              # src/common).
              make -C src/backend generated-headers
              make -C src/interfaces/libpq all
              make -j"$(sysctl -n hw.ncpu)" -C src/bin/pg_dump all
              make -j"$(sysctl -n hw.ncpu)" -C src/bin/psql all
              cp src/bin/pg_dump/pg_dump src/bin/pg_dump/pg_restore "$OUT"/
              cp src/bin/psql/psql "$OUT"/
              cp src/interfaces/libpq/libpq.5.dylib "$OUT"/libpq.5.dylib
              # Rewrite the absolute /tmp/pgbuild libpq install_name to a
              # relative @loader_path so the tools find libpq next to themselves.
              for b in pg_dump pg_restore psql; do
                libpq=$(otool -L "$OUT/$b" | awk '/libpq/ {print $1; exit}')
                install_name_tool -change "$libpq" "@loader_path/libpq.5.dylib" "$OUT/$b"
              done
              ;;
            windows-latest)
              URL="https://get.enterprisedb.com/postgresql/postgresql-${PG_VER}-1-windows-x64-binaries.zip"
              curl -fsSL "$URL" -o /tmp/pg.zip
              EXPECTED="3508d8f085bc3980f38211a82e3f31e5fcae9952105d3dc2f8be67b64a822baa"
              echo "$EXPECTED  /tmp/pg.zip" | sha256sum -c -
              sha256sum /tmp/pg.zip
              unzip -o /tmp/pg.zip -d /tmp/pg
              cp /tmp/pg/pgsql/bin/pg_dump.exe /tmp/pg/pgsql/bin/pg_restore.exe /tmp/pg/pgsql/bin/psql.exe "$OUT"/
              # pg_dump.exe needs far more than libpq.dll (libcrypto-3-x64.dll,
              # libssl-3-x64.dll, libiconv-2.dll, libintl-9.dll,
              # libwinpthread-1.dll, liblz4.dll, libzstd.dll, ICU DLLs, ...);
              # copy every DLL next to the tools.
              cp /tmp/pg/pgsql/bin/*.dll "$OUT"/
              ;;
          esac
          (cd "$OUT" && sha256sum * | tee checksums.txt)
          # Resolve the per-platform binary suffix WITHOUT a command
          # substitution: `$( [ ... ] && echo .exe )` returns exit 1 when the
          # test is false, and under `set -e` that aborts the whole step.
          if [ "${{ matrix.platform }}" = windows-latest ]; then
            BIN_EXT=".exe"
          else
            BIN_EXT=""
          fi
          for b in pg_dump pg_restore psql; do
            f="$OUT/${b}${BIN_EXT}"
            test -f "$f" || { echo "missing $f"; exit 1; }
          done
          # Sanity: every tool must run (loader path is correct) — this catches
          # a wrong @loader_path / rpath before we ship a broken bundle.
          for b in pg_dump pg_restore psql; do
            "$OUT/${b}${BIN_EXT}" --version >/dev/null 2>&1 || { echo "$b failed to run from resource dir"; exit 1; }
          done

      - name: Build MariaDB client tools (bundled)
        shell: bash
        run: |
          set -euo pipefail
          MARIADB_VER="11.4.5"
          OUT="${GITHUB_WORKSPACE}/src-tauri/resources/mysql_tools"
          mkdir -p "$OUT"
          case "${{ matrix.platform }}" in
            ubuntu-22.04)
              sudo apt-get update -y
              sudo apt-get install -y cmake build-essential libssl-dev libzstd-dev pkg-config libgnutls28-dev patchelf
              git clone --depth 1 --branch "mariadb-${MARIADB_VER}" https://github.com/MariaDB/server.git /tmp/mariadb-server
              cd /tmp/mariadb-server
              # Client-only build: wolfSSL + zlib are vendored. The in-tree
              # Connector C still probes GnuTLS on Unix regardless of WITH_SSL,
              # so libgnutls28-dev is required at build time.
              cmake -DCMAKE_BUILD_TYPE=Release \
                -DWITHOUT_SERVER=ON \
                -DWITHOUT_TOKUDB=1 \
                -DWITHOUT_ROCKSDB=1 \
                -DWITHOUT_MROONGA=1 \
                -DWITHOUT_SPIDER=1 \
                -DWITHOUT_SEQUENCE=1 \
                -DWITH_UNIT_TESTS=OFF \
                -DWITH_SSL=bundled \
                -DWITH_ZLIB=bundled .
              make -j"$(nproc)" mariadb-dump mariadb
              # Clients build into client/ (stable across MariaDB 10.x/11.x);
              # fall back to a broad search if a future version moves them.
              for b in mariadb-dump mariadb; do
                src=$(find client -maxdepth 1 -type f -name "$b" -print -quit 2>/dev/null || true)
                test -n "$src" || src=$(find . -type f -name "$b" -print -quit || true)
                test -n "$src" || { echo "build produced no $b binary"; exit 1; }
                cp "$src" "$OUT"/
              done
              # Bundle every non-core shared dependency (GnuTLS + its chain,
              # libzstd) next to the clients and point the loader at $ORIGIN —
              # the same pattern the pg_tools step uses for libpq.
              for b in mariadb-dump mariadb; do
                ldd "$OUT/$b" | awk '/=> \// {print $3}' | sort -u | while read -r lib; do
                  case "$(basename "$lib")" in
                    ld-linux*|libc.so*|libm.so*|libpthread*|libdl.so*|librt.so*|libgcc_s*|libstdc++*|libcrypt.so*|libresolv*|libutil*)
                      ;; # core runtime — present on every glibc system
                    *)
                      cp -n "$lib" "$OUT/$(basename "$lib")" 2>/dev/null || true
                      ;;
                  esac
                done
                patchelf --set-rpath '$ORIGIN' "$OUT/$b"
              done
              ;;
            macos-latest|macos-15-intel)
              git clone --depth 1 --branch "mariadb-${MARIADB_VER}" https://github.com/MariaDB/server.git /tmp/mariadb-server
              cd /tmp/mariadb-server
              # macOS connects the in-tree Connector C against brew OpenSSL
              # (proven to build+run on the runners); the Linux-only GnuTLS
              # probe does not fire here. Absolute brew paths are rewritten to
              # @loader_path below so user machines resolve them from the
              # resource dir.
              if [ "$(uname -m)" = arm64 ]; then
                SSL_DIR="/opt/homebrew/opt/openssl"
              else
                SSL_DIR="/usr/local/opt/openssl"
              fi
              [ -d "$SSL_DIR" ] || brew install openssl
              cmake -DCMAKE_BUILD_TYPE=Release \
                -DWITHOUT_SERVER=ON \
                -DWITHOUT_TOKUDB=1 \
                -DWITHOUT_ROCKSDB=1 \
                -DWITHOUT_MROONGA=1 \
                -DWITHOUT_SPIDER=1 \
                -DWITHOUT_SEQUENCE=1 \
                -DWITH_UNIT_TESTS=OFF \
                -DWITH_SSL="$SSL_DIR" \
                -DWITH_ZLIB=bundled .
              make -j"$(sysctl -n hw.ncpu)" mariadb-dump mariadb
              for b in mariadb-dump mariadb; do
                src=$(find client -maxdepth 1 -type f -name "$b" -print -quit 2>/dev/null || true)
                test -n "$src" || src=$(find . -type f -name "$b" -print -quit || true)
                test -n "$src" || { echo "build produced no $b binary"; exit 1; }
                cp "$src" "$OUT"/
              done
              # Rewrite absolute Homebrew dylib deps to @loader_path so the
              # bundled clients (and OpenSSL + their own deps) resolve from the
              # resource dir on user machines. Fixed-point loop: each pass
              # copies newly-seen dylibs and rewrites their parents' refs.
              # Ad-hoc re-sign after each edit — arm64 dyld refuses to load a
              # dylib whose (Homebrew) code signature was invalidated.
              for pass in 1 2 3 4 5 6; do
                for dylib in "$OUT"/*.dylib "$OUT"/mariadb-dump "$OUT"/mariadb; do
                  [ -f "$dylib" ] || continue
                  # `|| true` — with pipefail, grep exits 1 on zero matches.
                  otool -L "$dylib" | awk 'NR>1 {print $1}' | grep -E '^/(usr/local|opt/homebrew)' | while read -r lib; do
                    base=$(basename "$lib")
                    if [ ! -f "$OUT/$base" ]; then cp "$lib" "$OUT/$base" 2>/dev/null || true; fi
                    install_name_tool -change "$lib" "@loader_path/$base" "$dylib" 2>/dev/null || true
                  done || true
                  codesign --force --sign - "$dylib" 2>/dev/null || true
                done
              done
              ;;
            windows-latest)
              URL="https://archive.mariadb.org/mariadb-${MARIADB_VER}/winx64-packages/mariadb-${MARIADB_VER}-winx64.zip"
              curl -fsSL "$URL" -o /tmp/mariadb.zip
              # TODO(RELEASE): pin the sha256 printed on the first CI run, then
              # uncomment the check so later releases verify the download.
              # EXPECTED="<sha256 of mariadb-${MARIADB_VER}-winx64.zip>"
              # echo "$EXPECTED  /tmp/mariadb.zip" | sha256sum -c -
              sha256sum /tmp/mariadb.zip
              unzip -o /tmp/mariadb.zip -d /tmp/mariadb
              WINROOT="/tmp/mariadb/mariadb-${MARIADB_VER}-winx64"
              cp "$WINROOT"/bin/mariadb-dump.exe "$WINROOT"/bin/mariadb.exe "$OUT"/
              # The clients' only runtime dependency is the client library
              # (lib/libmariadb.dll); the winx64 package statically links
              # wolfSSL, so there are no OpenSSL DLLs to bundle. bin/*.dll is
              # just the embedded server.dll, which the client tools don't need.
              cp "$WINROOT"/lib/libmariadb.dll "$OUT"/
              ;;
          esac
          (cd "$OUT" && sha256sum * | tee checksums.txt)
          # Resolve the per-platform binary suffix WITHOUT a command
          # substitution (see the pg_tools step for why).
          if [ "${{ matrix.platform }}" = windows-latest ]; then
            BIN_EXT=".exe"
          else
            BIN_EXT=""
          fi
          for b in mariadb-dump mariadb; do
            f="$OUT/${b}${BIN_EXT}"
            test -f "$f" || { echo "missing $f"; exit 1; }
          done
          # Sanity: every tool must run (loader path is correct) — this catches
          # a missing shared library before we ship a broken bundle.
          for b in mariadb-dump mariadb; do
            "$OUT/${b}${BIN_EXT}" --version >/dev/null 2>&1 || { echo "$b failed to run from resource dir"; exit 1; }
          done

      - name: Build and upload to GitHub Release
        uses: tauri-apps/tauri-action@v0
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          tagName: ${{ github.ref_name }}
          releaseName: 'Gridline ${{ github.ref_name }}'
          releaseDraft: true
          args: ${{ matrix.args }}
          # Assets use tauri-action's default versioned naming (e.g.
          # Gridline_0.7.5_aarch64.dmg, Gridline-0.7.5-1.x86_64.rpm) and the
          # README download tables link to them statically per release —
          # remember to update both README tables when cutting a new version.