Add DEB, RPM, macOS, Windows and Helm build checks

These are the five QA builds that the Jenkins buildfarm still owns, and between
them they're the last thing standing in the way of retiring the QA half of it:
the container, tarball, wheel and documentation checks already run here.

The DEB and RPM builds run in a container per distribution on a hosted runner
rather than on a dedicated EC2 VM per distribution, which is the same build in
a reproducible environment and nine fewer machines to look after. The package
lists come from the buildfarm's own Ansible packages_build role. PGDG is
mandatory for RPM, because pkg/redhat/build.sh opens by globbing /usr/pgsql-1*
under `set -e` and dies without it; the DEB build is happy with the distro's
libpq-dev.

macOS builds on both architectures and is already proven: an earlier spike ran
it on macos-15 in 22 minutes with webpack peaking at 2991 MB against its 4096 MB
ceiling and no swap used at all, so the 7 GB arm64 runners have adequate
headroom. That spike workflow is removed here, superseded by the real one.

Windows runs on a hosted runner too, which wasn't obvious: signing in Make.bat
is conditional on PGADMIN_WINDOWS_CSC, so an unsigned build check needs neither
the Certum hardware token nor the machine it's plugged into, and only the
signing release builds need a self-hosted runner. Its dependencies come from
dpage/winpgbuild, the same place the buildfarm's winpgbuild-update job gets
them. This one hasn't had a successful run yet and should be expected to need a
few passes.

All five trigger on master and by hand, not on pull requests, matching what the
Jenkins jobs do today; whether the full matrix should also gate PRs is a
separate question.
This commit is contained in:
Dave Page committed 2026-09-16 12:03:20 +01:00
1 parent d486241b06
commit d0ce7ad552
6 files changed
+462 -152

No files matched your search

+105
View File
@@ -0,0 +1,105 @@
# Replaces the pgadmin4-deb-qa Jenkins job. That job runs `make debian` on a
# dedicated EC2 VM per distribution; here each distribution is a container on a
# hosted runner instead, which is the same build in a reproducible environment
# and no VMs to maintain.
#
# Triggered on master and by hand rather than on pull requests, matching what
# the Jenkins job does today (it polls git every five minutes). Running the
# full matrix on every PR is a separate decision.
name: Check DEB builds
on:
push:
branches: [ "master" ]
workflow_dispatch:
concurrency:
group: '${{ github.workflow }} @ ${{ github.ref }}'
cancel-in-progress: true
permissions:
contents: read
jobs:
build-deb:
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
include:
# "name" exists only because artifact names may not contain a colon.
- { name: 'debian-12', image: 'debian:12' }
- { name: 'debian-13', image: 'debian:13' }
- { name: 'ubuntu-2204', image: 'ubuntu:22.04' }
- { name: 'ubuntu-2404', image: 'ubuntu:24.04' }
- { name: 'ubuntu-2604', image: 'ubuntu:26.04' }
container: ${{ matrix.image }}
steps:
# Before checkout, so the action has git available and the container has
# the certificates it needs to fetch anything.
- name: Install the build environment
env:
DEBIAN_FRONTEND: noninteractive
run: |
apt-get update
apt-get install -y --no-install-recommends \
build-essential \
ca-certificates \
curl \
dh-exec \
dpkg-dev \
fakeroot \
git \
libffi-dev \
libkrb5-dev \
libpq-dev \
libreadline-dev \
libssl-dev \
lsb-release \
python3-dev \
python3-pip \
python3-sphinx \
python3-venv \
python3-wheel \
unzip \
wget \
zlib1g-dev
# sphinxcontrib-youtube has no distro package. --break-system-packages is
# needed from Debian 12 and Ubuntu 24.04 onwards (PEP 668), and is
# harmless before that, but the flag itself only exists on newer pip, so
# fall back rather than fail.
- name: Install the Sphinx extensions
run: |
python3 -m pip install --break-system-packages sphinxcontrib-youtube \
|| python3 -m pip install sphinxcontrib-youtube
- name: Install syft
run: curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin
- uses: actions/setup-node@v4
with:
node-version: '20.x'
- uses: actions/checkout@v4
# Corepack provisions the pinned Yarn from the packageManager field,
# matching run-javascript-tests.yml. The distro nodejs packages are
# avoided entirely: on Ubuntu 26.04 the distro node ships without npm,
# which is why the buildfarm has to pin NodeSource above it.
- name: Enable Corepack
run: corepack enable
- name: Check the DEB build
run: make debian
- name: Archive the packages
uses: actions/upload-artifact@v4
with:
name: pgadmin4-deb-build-output-${{ matrix.name }}
path: dist/*.deb
+41
View File
@@ -0,0 +1,41 @@
# Replaces the pgadmin4-helm-buildx-qa Jenkins job, which runs
# `helm package pkg/helm` and nothing else. Helm is installed explicitly rather
# than relied upon from the runner image, since what the images ship changes
# between versions.
name: Check Helm chart build
on:
push:
branches: [ "master" ]
workflow_dispatch:
concurrency:
group: '${{ github.workflow }} @ ${{ github.ref }}'
cancel-in-progress: true
permissions:
contents: read
jobs:
build-chart:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- name: Install Helm
run: |
curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 -o get-helm-3
chmod 700 get-helm-3
./get-helm-3
- name: Check the chart build
run: helm package pkg/helm
- name: Archive the chart
uses: actions/upload-artifact@v4
with:
name: pgadmin4-helm-build-output
path: pgadmin4-helm-*.tgz
+74
View File
@@ -0,0 +1,74 @@
# Replaces the pgadmin4-macos-qa Jenkins job. That job runs `make appbundle` on
# the two EC2 Mac workers; this runs the same build on hosted runners, which
# cover both architectures.
#
# The build is unsigned and unnotarised: pkg/mac/build.sh sets CODESIGN=0 and
# NOTARIZE=0 when pkg/mac/codesign.conf and pkg/mac/notarization.conf are
# absent, so this needs no secrets. Signing belongs to the release and snapshot
# builds.
#
# PostgreSQL comes from Homebrew for pg_config (psycopg builds against it) and
# for the four client binaries copied into the bundle. The release build uses
# a PostgreSQL built against its own OpenSSL and Kerberos; that difference
# does not matter for a build check, and _fixup_imports relocates Homebrew's
# libraries into the bundle correctly (verified by _verify_bundle_linkage,
# which fails the build if anything still points outside it).
name: Check macOS builds
on:
push:
branches: [ "master" ]
workflow_dispatch:
concurrency:
group: '${{ github.workflow }} @ ${{ github.ref }}'
cancel-in-progress: true
permissions:
contents: read
env:
# Matches DEFAULT_PYTHON_VERSION on the buildfarm.
PGADMIN_PYTHON_VERSION: '3.13.15'
jobs:
build-appbundle:
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
include:
- { name: 'arm64', runner: 'macos-15' }
- { name: 'x64', runner: 'macos-15-intel' }
timeout-minutes: 120
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20.x'
- name: Enable Corepack
run: corepack enable
# syft generates the SBOM, wget fetches Electron.
- name: Install the build dependencies
run: brew install postgresql@18 syft wget
- name: Check the appbundle build
run: |
PGADMIN_POSTGRES_DIR="$(brew --prefix postgresql@18)"
export PGADMIN_POSTGRES_DIR
echo "Using PostgreSQL from ${PGADMIN_POSTGRES_DIR}"
make appbundle
- name: Archive the disk image
uses: actions/upload-artifact@v4
with:
name: pgadmin4-macos-build-output-${{ matrix.name }}
path: dist/*.dmg
+114
View File
@@ -0,0 +1,114 @@
# Replaces the pgadmin4-rpm-qa Jenkins job, as check-deb-build.yml replaces the
# DEB one: a container per distribution on a hosted runner, rather than a
# dedicated EC2 VM per distribution.
#
# Unlike the DEB build, the PGDG repository is not optional here.
# pkg/redhat/build.sh opens with `export PATH=$(ls -d /usr/pgsql-1* | ...)` and
# runs under `set -e`, so the build dies immediately without a PGDG PostgreSQL
# installed.
#
# The QA job does not sign anything; `rpmsign --resign` is a separate step in
# the release and snapshot jobs.
name: Check RPM builds
on:
push:
branches: [ "master" ]
workflow_dispatch:
concurrency:
group: '${{ github.workflow }} @ ${{ github.ref }}'
cancel-in-progress: true
permissions:
contents: read
jobs:
build-rpm:
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
include:
# "name" exists only because artifact names may not contain a colon.
# "repo" selects the PGDG repository RPM, which is built per
# distribution family and major version.
- name: 'el-9'
image: 'almalinux:9'
repo: 'https://download.postgresql.org/pub/repos/yum/reporpms/EL-9-x86_64/pgdg-redhat-repo-latest.noarch.rpm'
- name: 'el-10'
image: 'almalinux:10'
repo: 'https://download.postgresql.org/pub/repos/yum/reporpms/EL-10-x86_64/pgdg-redhat-repo-latest.noarch.rpm'
- name: 'fedora-43'
image: 'fedora:43'
repo: 'https://download.postgresql.org/pub/repos/yum/reporpms/F-43-x86_64/pgdg-fedora-repo-latest.noarch.rpm'
- name: 'fedora-44'
image: 'fedora:44'
repo: 'https://download.postgresql.org/pub/repos/yum/reporpms/F-44-x86_64/pgdg-fedora-repo-latest.noarch.rpm'
container: ${{ matrix.image }}
steps:
# Before checkout, so the action has git available.
- name: Install the build environment
env:
PGDG_REPO: ${{ matrix.repo }}
run: |
dnf install -y dnf-plugins-core
# EL needs EPEL and CRB for the build dependencies; Fedora has
# everything in the main repositories.
if [ ! -f /etc/fedora-release ]; then
dnf install -y epel-release
dnf config-manager --set-enabled crb
fi
# The distro's own PostgreSQL module would shadow PGDG's packages.
dnf -qy module disable postgresql || true
dnf install -y --nogpgcheck "${PGDG_REPO}"
dnf install -y \
expect \
fakeroot \
git \
krb5-devel \
libpq5-devel \
make \
openssl-devel \
postgresql17-devel \
python3-devel \
python3-pip \
python3-sphinx \
rpm-build \
rpm-sign \
unzip \
wget \
zlib-devel
- name: Install the Sphinx extensions
run: python3 -m pip install --break-system-packages sphinxcontrib-youtube || python3 -m pip install sphinxcontrib-youtube
- name: Install syft
run: curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin
- uses: actions/setup-node@v4
with:
node-version: '20.x'
- uses: actions/checkout@v4
- name: Enable Corepack
run: corepack enable
- name: Check the RPM build
run: make redhat
- name: Archive the packages
uses: actions/upload-artifact@v4
with:
name: pgadmin4-rpm-build-output-${{ matrix.name }}
path: dist/*.rpm
+128
View File
@@ -0,0 +1,128 @@
# Replaces the pgadmin4-windows-x64-qa Jenkins job.
#
# This runs on a hosted runner, not a self-hosted one. Code signing in Make.bat
# is conditional on PGADMIN_WINDOWS_CSC being set (see Make.bat:339, :393 and
# :415), so an unsigned build check needs neither the Certum hardware token nor
# the machine it is plugged into. Only the release and snapshot builds, which
# do sign, need the self-hosted runner.
#
# Dependencies come from dpage/winpgbuild, which is exactly where the
# buildfarm's winpgbuild-update job gets them, so the toolchain matches what
# the release build uses rather than being an approximation of it.
#
# UNTESTED as written: unlike the macOS equivalent this has not yet had a
# successful run, so expect the first few attempts to need adjustment.
name: Check Windows build
on:
push:
branches: [ "master" ]
workflow_dispatch:
concurrency:
group: '${{ github.workflow }} @ ${{ github.ref }}'
cancel-in-progress: true
permissions:
contents: read
jobs:
build-installer:
runs-on: windows-2025
timeout-minutes: 120
env:
# The major version of the winpgbuild PostgreSQL release to pull, matching
# DEFAULT_POSTGRESQL_VERSION on the buildfarm.
POSTGRES_MAJOR: '18'
DEPS_DIR: 'C:\build64'
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20.x'
- name: Enable Corepack
run: corepack enable
- uses: actions/setup-python@v5
id: python
with:
python-version: '3.13'
# Make.bat drives the venv through virtualenv.exe rather than the stdlib
# venv module, and checks for it explicitly (Make.bat:146).
- name: Install virtualenv
run: python -m pip install --upgrade pip virtualenv
- name: Install Inno Setup
run: choco install innosetup -y --no-progress
# The same artefacts the buildfarm's winpgbuild-update job installs. The
# archives unpack to <DEPS_DIR>\postgresql and <DEPS_DIR>\krb5.
- name: Install the PostgreSQL and Kerberos dependencies
shell: pwsh
run: |
New-Item -ItemType Directory -Force -Path $env:DEPS_DIR | Out-Null
$pg = "postgresql-$env:POSTGRES_MAJOR-latest"
Invoke-WebRequest -Uri "https://github.com/dpage/winpgbuild/releases/download/$pg/$pg.zip" `
-OutFile "$env:DEPS_DIR\postgresql.zip"
Expand-Archive -Path "$env:DEPS_DIR\postgresql.zip" -DestinationPath $env:DEPS_DIR -Force
Invoke-WebRequest -Uri "https://github.com/dpage/winpgbuild/releases/download/krb5-latest/krb5-latest.zip" `
-OutFile "$env:DEPS_DIR\krb5.zip"
Expand-Archive -Path "$env:DEPS_DIR\krb5.zip" -DestinationPath $env:DEPS_DIR -Force
Get-ChildItem $env:DEPS_DIR
# The hosted image ships Visual Studio Enterprise at a path that differs
# from the Community install Make.bat defaults to, and the redistributable
# version directory changes with every VS update, so both are discovered
# rather than hardcoded.
- name: Locate the Visual Studio toolchain
shell: pwsh
run: |
$vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe"
$vs = & $vswhere -latest -products * -property installationPath
if (-not $vs) { throw "Could not locate a Visual Studio installation" }
Write-Output "Visual Studio: $vs"
$redist = Get-ChildItem "$vs\VC\Redist\MSVC" -Directory |
Where-Object { Test-Path "$($_.FullName)\vc_redist.x64.exe" } |
Sort-Object Name -Descending |
Select-Object -First 1
if (-not $redist) { throw "Could not locate vc_redist.x64.exe under $vs" }
Write-Output "VC++ redistributable: $($redist.FullName)"
"VS_INSTALL_DIR=$vs" | Out-File -FilePath $env:GITHUB_ENV -Append
"PGADMIN_VCREDIST_DIR=$($redist.FullName)" | Out-File -FilePath $env:GITHUB_ENV -Append
# psycopg[c] compiles against libpq, so the build needs the MSVC
# environment. vcvars64.bat only sets it for its own process, hence
# invoking it and Make.bat in a single cmd session, as the Jenkins job
# does.
- name: Check the installer build
shell: cmd
env:
PGADMIN_PYTHON_DIR: ${{ format('{0}', steps.python.outputs.python-path) }}
run: |
FOR %%I IN ("%PGADMIN_PYTHON_DIR%") DO SET "PGADMIN_PYTHON_DIR=%%~dpI"
IF "%PGADMIN_PYTHON_DIR:~-1%" == "\" SET "PGADMIN_PYTHON_DIR=%PGADMIN_PYTHON_DIR:~0,-1%"
SET "PGADMIN_POSTGRES_DIR=%DEPS_DIR%\postgresql"
SET "PGADMIN_KRB5_DIR=%DEPS_DIR%\krb5"
SET "PGADMIN_INNOTOOL_DIR=C:\Program Files (x86)\Inno Setup 6"
CALL "%VS_INSTALL_DIR%\VC\Auxiliary\Build\vcvars64.bat" || EXIT /b 1
CALL Make.bat || EXIT /b 1
- name: Archive the installer
uses: actions/upload-artifact@v4
with:
name: pgadmin4-windows-build-output
path: dist/*.exe
-152
View File
@@ -1,152 +0,0 @@
# Throwaway spike. NOT part of CI, and not wired to push or pull_request.
#
# One question: does `make appbundle` fit inside a GitHub-hosted macOS runner?
# The arm64 images have 3 vCPU and 7 GB of RAM, against the 8 cores and 16 GiB
# of the mac2.metal buildfarm worker they would replace, and the comment at
# pkg/mac/build-functions.sh:320 records webpack being OS-OOM-killed inside
# TerserPlugin on a Mac builder at a 3 GB old-space ceiling, with 6 GB then
# starving the earlier steps. Nothing in the existing workflows runs a
# production webpack bundle on macOS, so that history is untested here.
#
# The build runs unsigned: pkg/mac/build.sh sets CODESIGN=0 and NOTARIZE=0 when
# codesign.conf and notarization.conf are absent, so this needs no secrets.
#
# Delete this file once the question is answered.
name: Spike - macOS appbundle on hosted runners
on:
workflow_dispatch:
inputs:
runner:
description: 'Runner image to build on'
type: choice
default: 'macos-15'
options:
- 'macos-15'
- 'macos-26'
- 'macos-15-intel'
- 'macos-26-intel'
max_old_space:
description: 'Override the webpack V8 old-space ceiling, in MB. Leave empty to build exactly as the buildfarm does (4096).'
type: string
default: ''
python_version:
description: 'Python version to embed. Matches DEFAULT_PYTHON_VERSION on the buildfarm.'
type: string
default: '3.13.15'
permissions:
contents: read
jobs:
appbundle:
runs-on: ${{ inputs.runner }}
timeout-minutes: 180
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20.x'
# Corepack reads packageManager from the package.json files, matching
# what run-javascript-tests.yml does.
- name: Enable Corepack
run: corepack enable
- name: Record the runner specification
run: |
sw_vers
echo "Architecture: $(uname -m)"
echo "Logical CPUs: $(sysctl -n hw.logicalcpu)"
echo "Physical RAM: $(( $(sysctl -n hw.memsize) / 1024 / 1024 )) MB"
echo
sysctl -n vm.swapusage
echo
df -h /
# PostgreSQL supplies pg_dump, pg_dumpall, pg_restore and psql for the
# bundle, and pg_config for the psycopg[c] build. The buildfarm uses its
# own PostgreSQL built against its own OpenSSL and Kerberos; Homebrew's
# is fine here because we are measuring the build, not shipping the
# result. syft generates the SBOM; wget fetches Electron.
- name: Install build dependencies
run: brew install postgresql@18 syft wget
# The ceiling is hardcoded in build-functions.sh, so overriding it means
# editing the script. Acceptable in a throwaway spike, and it is what
# makes bisecting the ceiling possible; the default leaves it untouched.
- name: Override the webpack memory ceiling
if: inputs.max_old_space != ''
env:
MAX_OLD_SPACE: ${{ inputs.max_old_space }}
run: |
sed -i '' \
-E "s/--max-old-space-size=[0-9]+/--max-old-space-size=${MAX_OLD_SPACE}/" \
pkg/mac/build-functions.sh
grep -n 'max-old-space-size' pkg/mac/build-functions.sh
# Sampled rather than derived from a single rusage figure, because the
# build is a tree of short-lived processes and what we actually want to
# know is whether the machine as a whole ran out of headroom. Growing
# swap is the tell.
- name: Start the memory sampler
run: |
cat > "${RUNNER_TEMP}/sample-memory.sh" <<'EOF'
#!/bin/bash
while true; do
date -u +'--- %H:%M:%S'
sysctl -n vm.swapusage
ps -Ao rss=,comm= | sort -rn | head -3 | \
awk '{printf " %7.0f MB %s\n", $1 / 1024, $2}'
sleep 10
done
EOF
chmod +x "${RUNNER_TEMP}/sample-memory.sh"
"${RUNNER_TEMP}/sample-memory.sh" > "${RUNNER_TEMP}/memory.log" 2>&1 &
echo "SAMPLER_PID=$!" >> "${GITHUB_ENV}"
- name: Build the appbundle
env:
PGADMIN_PYTHON_VERSION: ${{ inputs.python_version }}
run: |
PGADMIN_POSTGRES_DIR="$(brew --prefix postgresql@18)"
export PGADMIN_POSTGRES_DIR
echo "Using PostgreSQL from ${PGADMIN_POSTGRES_DIR}"
/usr/bin/time -l make appbundle
- name: Stop the memory sampler
if: always()
run: kill "${SAMPLER_PID}" || true
- name: Report the peak memory and disk usage
if: always()
run: |
echo "=== Largest resident sets seen during the run ==="
grep ' MB ' "${RUNNER_TEMP}/memory.log" | sort -rn | head -20 || true
echo
echo "=== Swap ==="
awk '/^total/ {gsub(/M$/, "", $6); if ($6 + 0 > max) max = $6 + 0}
END {printf "Peak swap used during the run: %.0f MB\n", max}' \
"${RUNNER_TEMP}/memory.log" || true
echo -n "Swap now: "
sysctl -n vm.swapusage
echo
echo "=== Final disk ==="
df -h /
- name: Archive the memory log
if: always()
uses: actions/upload-artifact@v4
with:
name: memory-log-${{ inputs.runner }}
path: ${{ runner.temp }}/memory.log
- name: Archive the appbundle
if: success()
uses: actions/upload-artifact@v4
with:
name: appbundle-${{ inputs.runner }}
path: dist/*.dmg