Skip to content

test: fix MaxMetadataEnvVar.EnvVarRaisesLimit abort on slow runners #437

test: fix MaxMetadataEnvVar.EnvVarRaisesLimit abort on slow runners

test: fix MaxMetadataEnvVar.EnvVarRaisesLimit abort on slow runners #437

Workflow file for this run

name: ci
on:
workflow_dispatch:
inputs:
cmake_build_type:
description: "The CMake build type to use"
type: choice
required: true
default: Release
options:
- Release
- Debug
- RelWithDebInfo
push:
branches:
- '**'
tags:
- 'v*'
paths-ignore:
- '**/*.md'
env:
DOCKERHUB_IMAGE_NAME: gizmodata/gizmosql
DOCKERHUB_LTS_IMAGE_NAME: gizmodata/gizmosql-lts
GITHUB_IMAGE_NAME: ghcr.io/gizmodata/gizmosql
GITHUB_LTS_IMAGE_NAME: ghcr.io/gizmodata/gizmosql-lts
CMAKE_BUILD_TYPE: ${{ github.event.inputs.cmake_build_type || 'Release' }}
# Force the runner to host Node-20 JS actions on Node 24. Currently covers
# threeal/cmake-action@v2.1.0 and ilammy/msvc-dev-cmd@v1, which have no
# Node 24 release upstream yet. Becomes the default on 2026-06-02.
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
build-project-macos:
name: Build Project - MacOS (${{ matrix.duckdb_channel }})
runs-on: macos-14-xlarge
strategy:
fail-fast: false
matrix:
duckdb_channel: [stable, lts]
env:
# Stable produces gizmosql_cli_macos_arm64.zip (unchanged); LTS
# gets the _lts suffix so both can land in the release together.
zip_file_name: gizmosql_cli_macos_arm64${{ matrix.duckdb_channel == 'lts' && '_lts' || '' }}.zip
bin_suffix: ${{ matrix.duckdb_channel == 'lts' && '_lts' || '' }}
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Install build requirements
env:
HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK: 1
run: |
brew install automake boost gflags openssl@3
# Expose OpenSSL 3 to CMake's find_package(OpenSSL) for cpp-httplib.
# The macOS runner image no longer puts openssl@3 on the default
# CMake search path, AND Homebrew's openssl@3 formula now installs
# only static libs (libcrypto.a/libssl.a) without .dylib symlinks
# β€” so we must also set OPENSSL_USE_STATIC_LIBS=TRUE or
# find_package(OpenSSL) fails with "missing: Crypto SSL".
OPENSSL_PREFIX="$(brew --prefix openssl@3)"
echo "OPENSSL_ROOT_DIR=${OPENSSL_PREFIX}" >> "$GITHUB_ENV"
echo "OPENSSL_USE_STATIC_LIBS=TRUE" >> "$GITHUB_ENV"
# Belt-and-braces: explicitly pin the library/include paths so the
# cpp-httplib nested CMake's find_package(OpenSSL) short-circuits
# its search instead of (mysteriously) failing to locate libraries
# that are clearly present under OPENSSL_ROOT_DIR/lib.
echo "OPENSSL_INCLUDE_DIR=${OPENSSL_PREFIX}/include" >> "$GITHUB_ENV"
echo "OPENSSL_CRYPTO_LIBRARY=${OPENSSL_PREFIX}/lib/libcrypto.a" >> "$GITHUB_ENV"
echo "OPENSSL_SSL_LIBRARY=${OPENSSL_PREFIX}/lib/libssl.a" >> "$GITHUB_ENV"
- name: Restore Arrow cache
id: arrow-cache
uses: actions/cache/restore@v5
with:
path: build/third_party
key: arrow-macos-arm64-${{ matrix.duckdb_channel }}-apache-arrow-23.0.1-${{ hashFiles('third_party/Arrow_CMakeLists.txt.in', 'third_party/patch_arrow.cmake') }}
restore-keys: |
arrow-macos-arm64-${{ matrix.duckdb_channel }}-apache-arrow-23.0.1-
- name: Setup sccache
uses: mozilla-actions/sccache-action@v0.0.10
- name: Configure Project
uses: threeal/cmake-action@v2.1.0
with:
generator: Ninja
run-build: true
options: |
GIZMOSQL_ENTERPRISE=ON
WITH_OPENTELEMETRY=ON
GIZMOSQL_DUCKDB_CHANNEL=${{ matrix.duckdb_channel }}
CMAKE_C_COMPILER_LAUNCHER=sccache
CMAKE_CXX_COMPILER_LAUNCHER=sccache
OPENSSL_ROOT_DIR=${{ env.OPENSSL_ROOT_DIR }}
OPENSSL_USE_STATIC_LIBS=${{ env.OPENSSL_USE_STATIC_LIBS }}
OPENSSL_INCLUDE_DIR=${{ env.OPENSSL_INCLUDE_DIR }}
OPENSSL_CRYPTO_LIBRARY=${{ env.OPENSSL_CRYPTO_LIBRARY }}
OPENSSL_SSL_LIBRARY=${{ env.OPENSSL_SSL_LIBRARY }}
- name: Save Arrow cache
if: steps.arrow-cache.outputs.cache-hit != 'true'
uses: actions/cache/save@v5
with:
path: build/third_party
key: arrow-macos-arm64-${{ matrix.duckdb_channel }}-apache-arrow-23.0.1-${{ hashFiles('third_party/Arrow_CMakeLists.txt.in', 'third_party/patch_arrow.cmake') }}
- name: Run Tests (Core Edition - No License)
run: |
./build/tests/gizmosql_integration_tests
- name: Setup Enterprise License
env:
GIZMOSQL_LICENSE_KEY: ${{ secrets.GIZMOSQL_LICENSE_KEY }}
if: env.GIZMOSQL_LICENSE_KEY != ''
run: |
echo "$GIZMOSQL_LICENSE_KEY" > /tmp/gizmosql_license.jwt
echo "GIZMOSQL_LICENSE_KEY_FILE=/tmp/gizmosql_license.jwt" >> $GITHUB_ENV
- name: Run Tests (Enterprise Edition - With License)
env:
GIZMOSQL_LICENSE_KEY: ${{ secrets.GIZMOSQL_LICENSE_KEY }}
if: env.GIZMOSQL_LICENSE_KEY != ''
run: |
./build/tests/gizmosql_integration_tests
- name: Run Client Shell Tests
run: |
cd build && bash ../tests/test_client_shell.sh
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.12'
- name: Run Python Tests (macOS)
run: |
python3 -m venv /tmp/test_venv
source /tmp/test_venv/bin/activate
pip install adbc-driver-gizmosql geopandas shapely pyarrow duckdb
# Start server in background
./build/gizmosql_server${{ env.bin_suffix }} --password gizmosql_password &
SERVER_PID=$!
sleep 5
# Run Python tests β€” stop on first failure so we don't silently
# mask earlier failures with a later test's exit code.
TEST_EXIT_CODE=0
python tests/test_geoarrow.py || TEST_EXIT_CODE=$?
if [ $TEST_EXIT_CODE -eq 0 ]; then
python tests/test_bulk_ingest.py || TEST_EXIT_CODE=$?
fi
if [ $TEST_EXIT_CODE -eq 0 ]; then
python tests/test_autocommit_false.py || TEST_EXIT_CODE=$?
fi
if [ $TEST_EXIT_CODE -eq 0 ]; then
python tests/test_v1_22_features.py || TEST_EXIT_CODE=$?
fi
if [ $TEST_EXIT_CODE -eq 0 ]; then
python tests/test_v1_22_1_features.py || TEST_EXIT_CODE=$?
fi
# Stop server
kill $SERVER_PID 2>/dev/null || true
exit $TEST_EXIT_CODE
- name: Sign and notarize the server release build
uses: toitlang/action-macos-sign-notarize@v1.3.0
with:
certificate: ${{ secrets.APPLE_CERTIFICATE }}
certificate-password: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
username: ${{ secrets.APPLE_ID_USERNAME }}
password: ${{ secrets.APPLE_ID_PASSWORD }}
apple-team-id: ${{ secrets.APPLE_TEAM_ID }}
app-path: build/gizmosql_server${{ env.bin_suffix }}
entitlements-path: macos/entitlements.plist
- name: Sign and notarize the client release build
uses: toitlang/action-macos-sign-notarize@v1.3.0
with:
certificate: ${{ secrets.APPLE_CERTIFICATE }}
certificate-password: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
username: ${{ secrets.APPLE_ID_USERNAME }}
password: ${{ secrets.APPLE_ID_PASSWORD }}
apple-team-id: ${{ secrets.APPLE_TEAM_ID }}
app-path: build/gizmosql_client${{ env.bin_suffix }}
- name: Zip artifacts
run: |
mv build/gizmosql_server${{ env.bin_suffix }} build/gizmosql_client${{ env.bin_suffix }} .
zip -j ${{ env.zip_file_name }} gizmosql_server${{ env.bin_suffix }} gizmosql_client${{ env.bin_suffix }}
- name: Upload artifacts
uses: actions/upload-artifact@v7
with:
name: ${{ env.zip_file_name }}
path: |
${{ env.zip_file_name }}
build-project-linux:
name: Build Project - Linux ${{ matrix.platform }} (${{ matrix.duckdb_channel }})
strategy:
fail-fast: false
matrix:
include:
- platform: amd64
os: linux
runner: ubuntu-latest
duckdb_channel: stable
- platform: arm64
os: linux
runner: ubuntu-24.04-arm
duckdb_channel: stable
- platform: amd64
os: linux
runner: ubuntu-latest
duckdb_channel: lts
- platform: arm64
os: linux
runner: ubuntu-24.04-arm
duckdb_channel: lts
runs-on: ${{ matrix.runner }}
env:
zip_file_name: gizmosql_cli_${{ matrix.os }}_${{ matrix.platform }}${{ matrix.duckdb_channel == 'lts' && '_lts' || '' }}.zip
bin_suffix: ${{ matrix.duckdb_channel == 'lts' && '_lts' || '' }}
docker_image: ${{ matrix.duckdb_channel == 'lts' && 'gizmodata/gizmosql-lts' || 'gizmodata/gizmosql' }}
docker_image_ghcr: ${{ matrix.duckdb_channel == 'lts' && 'ghcr.io/gizmodata/gizmosql-lts' || 'ghcr.io/gizmodata/gizmosql' }}
services:
# PostgreSQL for general DuckLake tests
postgres:
image: postgres:16-alpine
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: testpassword
POSTGRES_DB: ducklake_catalog
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
# PostgreSQL for instrumentation tests (isolated)
postgres-instrumentation:
image: postgres:16-alpine
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: testpassword
POSTGRES_DB: instrumentation_catalog
ports:
- 5433:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- name: Checkout
uses: actions/checkout@v6
# Build deps (compilers, Boost, OpenSSL, ...) live inside the manylinux
# build container β€” the host only needs docker, python, and zip (all
# preinstalled on the runner image).
- name: Restore Arrow cache
id: arrow-cache
uses: actions/cache/restore@v5
with:
# portable-deps holds the static OpenSSL + Boost prefixes built by
# scripts/build_portable_linux.sh (stamp-file guarded).
path: |
build/third_party
build/portable-deps
key: arrow-linux-portable228-v2-${{ matrix.platform }}-${{ matrix.duckdb_channel }}-apache-arrow-23.0.1-${{ hashFiles('third_party/Arrow_CMakeLists.txt.in', 'third_party/patch_arrow.cmake', 'scripts/build_portable_linux.sh') }}
restore-keys: |
arrow-linux-portable228-v2-${{ matrix.platform }}-${{ matrix.duckdb_channel }}-apache-arrow-23.0.1-
- name: Setup sccache
uses: mozilla-actions/sccache-action@v0.0.10
- name: Build (manylinux_2_28 container β€” glibc 2.28 baseline)
# Compile inside AlmaLinux 8 (glibc 2.28) with gcc-toolset-14 so the
# released binaries run out of the box on Raspberry Pi OS (bullseye+),
# Amazon Linux 2023, Ubuntu 20.04+, Debian 11+, etc. Same gcc 14
# codegen as the Ubuntu runner β€” only the glibc *baseline* changes;
# glibc's optimized routines are chosen at runtime on the target via
# IFUNC, so there is no performance cost. The script self-checks the
# produced binaries' max GLIBC/GLIBCXX symbol versions and fails the
# build on any baseline regression. sccache (a static musl binary,
# arch-matched) is mounted in and the GHA cache env forwarded so
# compile caching keeps working inside the container.
# The workspace is mounted at the SAME path as on the host so that
# absolute paths CMake bakes into build artifacts (e.g. the
# $<TARGET_FILE:gizmosql_client> path compiled into the integration
# test binary) remain valid when the tests later run on the host.
run: |
docker run --rm \
-v "$PWD":"$PWD" \
-v "$SCCACHE_PATH":/usr/local/bin/sccache:ro \
-e REPO_ROOT="$PWD" \
-e CMAKE_BUILD_TYPE=${{ env.CMAKE_BUILD_TYPE }} \
-e SCCACHE_GHA_ENABLED \
-e ACTIONS_CACHE_URL \
-e ACTIONS_RESULTS_URL \
-e ACTIONS_RUNTIME_TOKEN \
"quay.io/pypa/manylinux_2_28_$(uname -m)" \
bash "$PWD/scripts/build_portable_linux.sh" ${{ matrix.duckdb_channel }} build
sudo chown -R "$(id -u):$(id -g)" build
- name: Smoke test out-of-the-box startup (Raspberry Pi OS userland + Amazon Linux 2023)
# debian:bookworm-slim is the same userland as Raspberry Pi OS
# (bookworm). Both targets were broken before the manylinux build
# (binaries needed GLIBC_2.38 / GLIBCXX_3.4.32).
run: |
docker run --rm -v "$PWD/build:/bin-under-test:ro" debian:bookworm-slim bash -c \
"apt-get update -qq >/dev/null && apt-get install -y -qq libcurl4 >/dev/null && /bin-under-test/gizmosql_server${{ env.bin_suffix }} --version"
docker run --rm -v "$PWD/build:/bin-under-test:ro" amazonlinux:2023 bash -c \
"/bin-under-test/gizmosql_server${{ env.bin_suffix }} --version"
- name: Save Arrow cache
if: steps.arrow-cache.outputs.cache-hit != 'true'
uses: actions/cache/save@v5
with:
path: |
build/third_party
build/portable-deps
key: arrow-linux-portable228-v2-${{ matrix.platform }}-${{ matrix.duckdb_channel }}-apache-arrow-23.0.1-${{ hashFiles('third_party/Arrow_CMakeLists.txt.in', 'third_party/patch_arrow.cmake', 'scripts/build_portable_linux.sh') }}
- name: Start MinIO and setup bucket
run: |
# Start MinIO server using docker (matching docker-compose.test.yml)
docker run -d --name minio \
-p 9000:9000 -p 9001:9001 \
-e MINIO_ROOT_USER=minioadmin \
-e MINIO_ROOT_PASSWORD=minioadmin \
minio/minio:latest server /data --console-address ":9001"
# Install MinIO client (architecture-aware)
ARCH=$(uname -m)
if [ "$ARCH" = "aarch64" ] || [ "$ARCH" = "arm64" ]; then
MC_ARCH="arm64"
else
MC_ARCH="amd64"
fi
curl -sL "https://dl.min.io/client/mc/release/linux-${MC_ARCH}/mc" -o /tmp/mc
chmod +x /tmp/mc
# Wait for MinIO to be ready and create bucket
for i in {1..30}; do
/tmp/mc alias set myminio http://localhost:9000 minioadmin minioadmin && break
echo "Waiting for MinIO to be ready... (attempt $i)"
sleep 2
done
/tmp/mc mb --ignore-existing myminio/instrumentation
- name: Run Tests (Core Edition - No License)
run: |
./build/tests/gizmosql_integration_tests
- name: Setup Enterprise License
env:
GIZMOSQL_LICENSE_KEY: ${{ secrets.GIZMOSQL_LICENSE_KEY }}
if: env.GIZMOSQL_LICENSE_KEY != ''
run: |
echo "$GIZMOSQL_LICENSE_KEY" > /tmp/gizmosql_license.jwt
echo "GIZMOSQL_LICENSE_KEY_FILE=/tmp/gizmosql_license.jwt" >> $GITHUB_ENV
- name: Run Tests (Enterprise Edition - With License)
env:
GIZMOSQL_LICENSE_KEY: ${{ secrets.GIZMOSQL_LICENSE_KEY }}
if: env.GIZMOSQL_LICENSE_KEY != ''
run: |
./build/tests/gizmosql_integration_tests
- name: Run Client Shell Tests
run: |
cd build && bash ../tests/test_client_shell.sh
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.12'
- name: Run Python Tests (Linux)
run: |
python3 -m venv /tmp/test_venv
source /tmp/test_venv/bin/activate
pip install adbc-driver-gizmosql geopandas shapely pyarrow duckdb
# Start server in background
./build/gizmosql_server${{ env.bin_suffix }} --password gizmosql_password &
SERVER_PID=$!
sleep 5
# Run Python tests β€” stop on first failure so we don't silently
# mask earlier failures with a later test's exit code.
TEST_EXIT_CODE=0
python tests/test_geoarrow.py || TEST_EXIT_CODE=$?
if [ $TEST_EXIT_CODE -eq 0 ]; then
python tests/test_bulk_ingest.py || TEST_EXIT_CODE=$?
fi
if [ $TEST_EXIT_CODE -eq 0 ]; then
python tests/test_autocommit_false.py || TEST_EXIT_CODE=$?
fi
if [ $TEST_EXIT_CODE -eq 0 ]; then
python tests/test_v1_22_features.py || TEST_EXIT_CODE=$?
fi
if [ $TEST_EXIT_CODE -eq 0 ]; then
python tests/test_v1_22_1_features.py || TEST_EXIT_CODE=$?
fi
# Stop server
kill $SERVER_PID 2>/dev/null || true
exit $TEST_EXIT_CODE
- name: Zip artifacts
run: |
mv build/gizmosql_server${{ env.bin_suffix }} build/gizmosql_client${{ env.bin_suffix }} .
zip -j ${{ env.zip_file_name }} gizmosql_server${{ env.bin_suffix }} gizmosql_client${{ env.bin_suffix }}
- name: Upload artifacts
uses: actions/upload-artifact@v7
with:
name: ${{ env.zip_file_name }}
path: |
${{ env.zip_file_name }}
- name: Stage executables for Docker build
# Dockerfile.ci / Dockerfile-slim.ci COPY gizmosql_server and
# gizmosql_client by hard-coded path. For the LTS channel the
# binaries are built as gizmosql_server_lts / gizmosql_client_lts;
# rename them at the source so the same Dockerfiles work for both
# channels and the resulting image keeps a stable in-container
# binary name (/usr/local/bin/gizmosql_server).
if: matrix.duckdb_channel == 'lts'
run: |
mv gizmosql_server_lts gizmosql_server
mv gizmosql_client_lts gizmosql_client
- name: Login to Docker Hub
uses: docker/login-action@v4
with:
registry: docker.io
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Login to GitHub Container Registry
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
# Build each platform by digest with SLSA provenance + SBOM attestations.
# The per-platform digests are merged into a multi-arch manifest (carrying
# the attestations forward) in the update-image-manifest job. On non-tag
# builds we build to cache only β€” validate the Dockerfile without pushing.
- name: Build and push full Docker image (by digest)
id: build-full
uses: docker/build-push-action@v7
with:
context: .
platforms: linux/${{ matrix.platform }}
file: Dockerfile.ci
# The DuckDB CLI tag baked in for ad-hoc shell use must match
# the channel; default in the Dockerfile is the stable version.
build-args: |
DUCKDB_VERSION=${{ matrix.duckdb_channel == 'lts' && '1.4.4' || '1.5.3' }}
no-cache: true
provenance: ${{ startsWith(github.ref, 'refs/tags/') && 'mode=max' || 'false' }}
sbom: ${{ startsWith(github.ref, 'refs/tags/') }}
outputs: ${{ startsWith(github.ref, 'refs/tags/') && format('type=image,"name={0},{1}",push-by-digest=true,name-canonical=true,push=true', env.docker_image, env.docker_image_ghcr) || 'type=cacheonly' }}
- name: Export full image digest
if: startsWith(github.ref, 'refs/tags/')
run: |
mkdir -p /tmp/digests/full
digest="${{ steps.build-full.outputs.digest }}"
touch "/tmp/digests/full/${digest#sha256:}"
- name: Upload full image digest
if: startsWith(github.ref, 'refs/tags/')
uses: actions/upload-artifact@v7
with:
name: digests-${{ matrix.duckdb_channel }}-full-${{ matrix.platform }}
path: /tmp/digests/full/*
if-no-files-found: error
retention-days: 1
- name: Build and push slim Docker image (by digest)
id: build-slim
uses: docker/build-push-action@v7
with:
context: .
platforms: linux/${{ matrix.platform }}
file: Dockerfile-slim.ci
no-cache: true
provenance: ${{ startsWith(github.ref, 'refs/tags/') && 'mode=max' || 'false' }}
sbom: ${{ startsWith(github.ref, 'refs/tags/') }}
outputs: ${{ startsWith(github.ref, 'refs/tags/') && format('type=image,"name={0},{1}",push-by-digest=true,name-canonical=true,push=true', env.docker_image, env.docker_image_ghcr) || 'type=cacheonly' }}
- name: Export slim image digest
if: startsWith(github.ref, 'refs/tags/')
run: |
mkdir -p /tmp/digests/slim
digest="${{ steps.build-slim.outputs.digest }}"
touch "/tmp/digests/slim/${digest#sha256:}"
- name: Upload slim image digest
if: startsWith(github.ref, 'refs/tags/')
uses: actions/upload-artifact@v7
with:
name: digests-${{ matrix.duckdb_channel }}-slim-${{ matrix.platform }}
path: /tmp/digests/slim/*
if-no-files-found: error
retention-days: 1
build-project-windows:
name: Build Project - Windows (${{ matrix.platform }}, ${{ matrix.duckdb_channel }})
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
duckdb_channel: [stable, lts]
# platform = release-artifact arch label (matches Linux naming);
# vc_arch = the MSVC/vcpkg arch token (vcvarsall arg, vcpkg triplet
# prefix, VC redist subdir, cache keys).
platform: [amd64, arm64]
include:
- platform: amd64
runner: windows-2022
vc_arch: x64
- platform: arm64
# Native ARM64 hosted runner (free for public repos). The image
# ships VS 2022 Enterprise + vcpkg at C:\vcpkg like windows-2022.
runner: windows-11-arm
vc_arch: arm64
env:
VCPKG_DEFAULT_TRIPLET: ${{ matrix.vc_arch }}-windows-static
# VS 2022 sets VCPKG_ROOT to its own vcpkg copy, conflicting with the
# runner's vcpkg at C:\vcpkg. Override to suppress the mismatch warning.
VCPKG_ROOT: C:\vcpkg
bin_suffix: ${{ matrix.duckdb_channel == 'lts' && '_lts' || '' }}
unsigned_artifact: gizmosql-windows-${{ matrix.platform }}-unsigned${{ matrix.duckdb_channel == 'lts' && '-lts' || '' }}
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Free disk space
shell: pwsh
run: |
@(
"C:\Program Files\Android",
"C:\Program Files\Eclipse Adoptium",
"C:\ghcup",
"$env:AGENT_TOOLSDIRECTORY"
) | ForEach-Object {
if (Test-Path $_) {
Write-Host "Removing $_"
Remove-Item -Recurse -Force $_ -ErrorAction SilentlyContinue
}
}
Get-PSDrive C | Select-Object Name, @{N='Free(GB)';E={[math]::Round($_.Free/1GB,1)}}
- name: Restore vcpkg cache
id: vcpkg-cache
uses: actions/cache/restore@v5
with:
path: ${{ env.VCPKG_ROOT }}/installed
key: vcpkg-${{ env.VCPKG_DEFAULT_TRIPLET }}-${{ hashFiles('.github/workflows/ci.yml') }}
restore-keys: |
vcpkg-${{ env.VCPKG_DEFAULT_TRIPLET }}-
- name: Install vcpkg dependencies
if: steps.vcpkg-cache.outputs.cache-hit != 'true'
run: |
& "$env:VCPKG_ROOT\vcpkg.exe" install boost-program-options boost-algorithm boost-uuid zlib openssl --triplet $env:VCPKG_DEFAULT_TRIPLET --clean-after-build --x-install-root="$env:VCPKG_ROOT\installed"
- name: Save vcpkg cache
if: steps.vcpkg-cache.outputs.cache-hit != 'true'
uses: actions/cache/save@v5
with:
path: ${{ env.VCPKG_ROOT }}/installed
key: vcpkg-${{ env.VCPKG_DEFAULT_TRIPLET }}-${{ hashFiles('.github/workflows/ci.yml') }}
# ilammy passes the arch through to vcvarsall.bat verbatim. On the
# windows-11-arm runner `arm64` selects the native ARM64-hosted
# toolchain (host arch is auto-detected from PROCESSOR_ARCHITECTURE).
- name: Setup MSVC
uses: ilammy/msvc-dev-cmd@v1
with:
arch: ${{ matrix.vc_arch }}
# vcvarsall.bat (run by ilammy/msvc-dev-cmd) exports VCPKG_ROOT pointing
# to VS's own vcpkg copy, overriding our job-level env. Re-set it.
- name: Restore VCPKG_ROOT after MSVC setup
shell: pwsh
run: echo "VCPKG_ROOT=C:\vcpkg" >> $env:GITHUB_ENV
- name: Restore Arrow cache
id: arrow-cache
uses: actions/cache/restore@v5
with:
path: build/third_party
key: arrow-windows-${{ matrix.vc_arch }}-${{ matrix.duckdb_channel }}-apache-arrow-23.0.1-${{ hashFiles('third_party/Arrow_CMakeLists.txt.in', 'third_party/patch_arrow.cmake') }}
restore-keys: |
arrow-windows-${{ matrix.vc_arch }}-${{ matrix.duckdb_channel }}-apache-arrow-23.0.1-
- name: Setup sccache
uses: mozilla-actions/sccache-action@v0.0.10
- name: Configure
run: |
cmake -B build -G Ninja `
-DCMAKE_BUILD_TYPE=${{ env.CMAKE_BUILD_TYPE }} `
-DCMAKE_C_COMPILER_LAUNCHER=sccache `
-DCMAKE_CXX_COMPILER_LAUNCHER=sccache `
-DCMAKE_TOOLCHAIN_FILE="$env:VCPKG_ROOT\scripts\buildsystems\vcpkg.cmake" `
-DVCPKG_TARGET_TRIPLET=${{ env.VCPKG_DEFAULT_TRIPLET }} `
-DVCPKG_MANIFEST_MODE=OFF `
-DGIZMOSQL_ENTERPRISE=ON `
-DGIZMOSQL_DUCKDB_CHANNEL=${{ matrix.duckdb_channel }}
- name: Build
run: cmake --build build --config ${{ env.CMAKE_BUILD_TYPE }}
- name: Save Arrow cache
if: steps.arrow-cache.outputs.cache-hit != 'true'
uses: actions/cache/save@v5
with:
path: build/third_party
key: arrow-windows-${{ matrix.vc_arch }}-${{ matrix.duckdb_channel }}-apache-arrow-23.0.1-${{ hashFiles('third_party/Arrow_CMakeLists.txt.in', 'third_party/patch_arrow.cmake') }}
- name: Copy VC++ runtime DLLs for DuckDB extension compatibility
run: |
$crtDir = Join-Path $env:VCToolsRedistDir "${{ matrix.vc_arch }}\Microsoft.VC143.CRT"
Copy-Item "$crtDir\vcruntime140.dll" build\
Copy-Item "$crtDir\vcruntime140_1.dll" build\
Copy-Item "$crtDir\msvcp140.dll" build\
- name: Run Tests
run: .\build\tests\gizmosql_integration_tests.exe
- name: Upload unsigned executables for signing
uses: actions/upload-artifact@v7
with:
name: ${{ env.unsigned_artifact }}
path: |
build/gizmosql_server${{ env.bin_suffix }}.exe
build/gizmosql_client${{ env.bin_suffix }}.exe
build/vcruntime140.dll
build/vcruntime140_1.dll
build/msvcp140.dll
sign-windows:
name: Sign Windows (${{ matrix.platform }}, ${{ matrix.duckdb_channel }})
needs: build-project-windows
runs-on: windows-latest
environment: production
strategy:
fail-fast: false
matrix:
duckdb_channel: [stable, lts]
platform: [amd64, arm64]
env:
unsigned_artifact: gizmosql-windows-${{ matrix.platform }}-unsigned${{ matrix.duckdb_channel == 'lts' && '-lts' || '' }}
signed_artifact: gizmosql-windows-${{ matrix.platform }}-signed${{ matrix.duckdb_channel == 'lts' && '-lts' || '' }}
cli_zip_name: gizmosql_cli_windows_${{ matrix.platform }}${{ matrix.duckdb_channel == 'lts' && '_lts' || '' }}.zip
permissions:
id-token: write
contents: read
steps:
- name: Download unsigned executables
uses: actions/download-artifact@v8
with:
name: ${{ env.unsigned_artifact }}
path: unsigned
- name: Azure Login
uses: azure/login@v3
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
allow-no-subscriptions: true
- name: Install AzureSignTool
run: dotnet tool install --global AzureSignTool
- name: Sign executables
run: |
Get-ChildItem -Path unsigned -Filter *.exe -Recurse | ForEach-Object {
AzureSignTool sign `
--azure-key-vault-url "${{ secrets.AZURE_KEY_VAULT_URI }}" `
--azure-key-vault-certificate "${{ secrets.AZURE_CERT_NAME }}" `
--azure-key-vault-managed-identity `
--timestamp-rfc3161 http://timestamp.digicert.com `
--timestamp-digest sha256 `
--file-digest sha256 `
--verbose `
$_.FullName
}
# Include the VC++ runtime DLLs (already Microsoft-signed) alongside our
# signed exes: the MSI job packages them from this artifact so the DLL
# arch always matches the binaries (the MSI runner's own System32 DLLs
# are x64 and would silently break the arm64 installer).
- name: Upload signed executables
uses: actions/upload-artifact@v7
with:
name: ${{ env.signed_artifact }}
path: |
unsigned/**/*.exe
unsigned/**/*.dll
if-no-files-found: error
- name: Create signed zip
run: |
7z a ${{ env.cli_zip_name }} .\unsigned\*.exe .\unsigned\*.dll
- name: Upload signed zip
uses: actions/upload-artifact@v7
with:
name: ${{ env.cli_zip_name }}
path: ${{ env.cli_zip_name }}
build-msi:
name: Build MSI (${{ matrix.platform }}, ${{ matrix.duckdb_channel }})
needs: sign-windows
runs-on: windows-latest
environment: production
strategy:
fail-fast: false
matrix:
duckdb_channel: [stable, lts]
platform: [amd64, arm64]
env:
signed_artifact: gizmosql-windows-${{ matrix.platform }}-signed${{ matrix.duckdb_channel == 'lts' && '-lts' || '' }}
msi_artifact: GizmoSQL-${{ matrix.platform }}${{ matrix.duckdb_channel == 'lts' && '-lts' || '' }}-msi.zip
msi_filename: GizmoSQL-${{ matrix.platform }}${{ matrix.duckdb_channel == 'lts' && '-lts' || '' }}.msi
bin_suffix: ${{ matrix.duckdb_channel == 'lts' && '_lts' || '' }}
# WiX arch token: x64 for amd64, arm64 as-is.
wix_arch: ${{ matrix.platform == 'arm64' && 'arm64' || 'x64' }}
permissions:
id-token: write
contents: read
steps:
- name: Checkout
uses: actions/checkout@v6
# The signed artifact already carries the arch-matched VC++ runtime
# DLLs (vcruntime140, vcruntime140_1, msvcp140) from the build runner's
# VC redist β€” do NOT copy them from this runner's System32, which is
# always x64.
- name: Download signed executables
uses: actions/download-artifact@v8
with:
name: ${{ env.signed_artifact }}
path: installer/bin
- name: Stage binaries for WiX (LTS strips the _lts suffix)
# GizmoSQL.wxs references gizmosql_server.exe / gizmosql_client.exe
# by literal filename. For the LTS channel the signed binaries
# carry the _lts suffix, so rename them in place β€” the MSI itself
# is the channel marker (filename is GizmoSQL-amd64-lts.msi).
if: matrix.duckdb_channel == 'lts'
run: |
Move-Item installer\bin\gizmosql_server_lts.exe installer\bin\gizmosql_server.exe -Force
Move-Item installer\bin\gizmosql_client_lts.exe installer\bin\gizmosql_client.exe -Force
- name: Install WiX Toolset
run: |
dotnet tool install --global wix --version 6.0.2
wix extension add WixToolset.UI.wixext/6.0.2
- name: Convert logo to BMP
run: |
Add-Type -AssemblyName System.Drawing
$logo = [System.Drawing.Image]::FromFile("${{ github.workspace }}\installer\gizmosql_logo.png")
# Banner (493x58)
$banner = New-Object System.Drawing.Bitmap(493, 58)
$g = [System.Drawing.Graphics]::FromImage($banner)
$g.Clear([System.Drawing.Color]::White)
$g.DrawImage($logo, 10, 4, 200, 50)
$g.Dispose()
$banner.Save("${{ github.workspace }}\installer\banner.bmp", [System.Drawing.Imaging.ImageFormat]::Bmp)
# Dialog (493x312)
$dialog = New-Object System.Drawing.Bitmap(493, 312)
$g = [System.Drawing.Graphics]::FromImage($dialog)
$g.Clear([System.Drawing.Color]::White)
$g.DrawImage($logo, 20, 20, 300, 75)
$g.Dispose()
$dialog.Save("${{ github.workspace }}\installer\dialog.bmp", [System.Drawing.Imaging.ImageFormat]::Bmp)
$logo.Dispose()
- name: Create license.rtf
run: |
$license = Get-Content "${{ github.workspace }}\LICENSE" -Raw
$rtf = "{\rtf1\ansi\deff0 {\fonttbl {\f0 Consolas;}}\f0\fs18 " + ($license -replace "`n", "\par ") + "}"
Set-Content -Path "${{ github.workspace }}\installer\license.rtf" -Value $rtf
- name: Build MSI
run: |
wix build `
"${{ github.workspace }}\installer\GizmoSQL.wxs" `
-arch ${{ env.wix_arch }} `
-b BinDir="${{ github.workspace }}\installer\bin" `
-b "${{ github.workspace }}\installer" `
-o "${{ github.workspace }}\installer\${{ env.msi_filename }}" `
-ext WixToolset.UI.wixext
- name: Azure Login
uses: azure/login@v3
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
allow-no-subscriptions: true
- name: Install AzureSignTool
run: dotnet tool install --global AzureSignTool
- name: Sign MSI
run: |
AzureSignTool sign `
--azure-key-vault-url "${{ secrets.AZURE_KEY_VAULT_URI }}" `
--azure-key-vault-certificate "${{ secrets.AZURE_CERT_NAME }}" `
--azure-key-vault-managed-identity `
--timestamp-rfc3161 http://timestamp.digicert.com `
--timestamp-digest sha256 `
--file-digest sha256 `
--verbose `
"${{ github.workspace }}\installer\${{ env.msi_filename }}"
- name: Upload MSI
uses: actions/upload-artifact@v7
with:
name: ${{ env.msi_artifact }}
path: installer/${{ env.msi_filename }}
if-no-files-found: error
create-release:
name: Create a release
if: startsWith(github.ref, 'refs/tags/')
needs: [build-project-macos, build-project-linux, build-project-windows, build-msi, build-ios]
runs-on: ubuntu-latest
permissions:
contents: write
id-token: write
attestations: write
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Download CLI zip artifacts
uses: actions/download-artifact@v8
with:
path: artifacts
pattern: gizmosql_cli_*.zip
merge-multiple: true
- name: Download MSI artifacts
uses: actions/download-artifact@v8
with:
path: artifacts
pattern: GizmoSQL-*-msi.zip
merge-multiple: true
- name: Attest build provenance - Linux AMD64
uses: actions/attest-build-provenance@v4
with:
subject-path: artifacts/gizmosql_cli_linux_amd64.zip
- name: Attest build provenance - Linux ARM64
uses: actions/attest-build-provenance@v4
with:
subject-path: artifacts/gizmosql_cli_linux_arm64.zip
- name: Attest build provenance - macOS ARM64
uses: actions/attest-build-provenance@v4
with:
subject-path: artifacts/gizmosql_cli_macos_arm64.zip
- name: Attest build provenance - Windows AMD64
uses: actions/attest-build-provenance@v4
with:
subject-path: artifacts/gizmosql_cli_windows_amd64.zip
- name: Attest build provenance - Windows AMD64 MSI
uses: actions/attest-build-provenance@v4
with:
subject-path: artifacts/GizmoSQL-amd64.msi
- name: Attest build provenance - Windows ARM64
uses: actions/attest-build-provenance@v4
with:
subject-path: artifacts/gizmosql_cli_windows_arm64.zip
- name: Attest build provenance - Windows ARM64 MSI
uses: actions/attest-build-provenance@v4
with:
subject-path: artifacts/GizmoSQL-arm64.msi
# ----- LTS channel artifacts -----
- name: Attest build provenance - Linux AMD64 (LTS)
uses: actions/attest-build-provenance@v4
with:
subject-path: artifacts/gizmosql_cli_linux_amd64_lts.zip
- name: Attest build provenance - Linux ARM64 (LTS)
uses: actions/attest-build-provenance@v4
with:
subject-path: artifacts/gizmosql_cli_linux_arm64_lts.zip
- name: Attest build provenance - macOS ARM64 (LTS)
uses: actions/attest-build-provenance@v4
with:
subject-path: artifacts/gizmosql_cli_macos_arm64_lts.zip
- name: Attest build provenance - Windows AMD64 (LTS)
uses: actions/attest-build-provenance@v4
with:
subject-path: artifacts/gizmosql_cli_windows_amd64_lts.zip
- name: Attest build provenance - Windows AMD64 MSI (LTS)
uses: actions/attest-build-provenance@v4
with:
subject-path: artifacts/GizmoSQL-amd64-lts.msi
- name: Attest build provenance - Windows ARM64 (LTS)
uses: actions/attest-build-provenance@v4
with:
subject-path: artifacts/gizmosql_cli_windows_arm64_lts.zip
- name: Attest build provenance - Windows ARM64 MSI (LTS)
uses: actions/attest-build-provenance@v4
with:
subject-path: artifacts/GizmoSQL-arm64-lts.msi
- name: Extract release notes from CHANGELOG.md
id: changelog
run: |
# Strip the leading 'v' from the tag (refs/tags/v1.2.3 -> 1.2.3)
# and pull the matching "## [VERSION] - DATE" section out of
# CHANGELOG.md (stop at the next "## [" heading). Save to
# release_notes.md; fall back to an empty file if the section
# isn't present so the release still gets created.
VERSION="${GITHUB_REF_NAME#v}"
echo "Extracting release notes for version: ${VERSION}"
awk -v ver="${VERSION}" '
$0 ~ "^## \\[" ver "\\]" { found=1; next }
found && /^## \[/ { exit }
found { print }
' CHANGELOG.md > release_notes.md
if [ -s release_notes.md ]; then
echo "---- release_notes.md ----"
cat release_notes.md
else
echo "WARNING: no matching CHANGELOG section for ${VERSION}; release will use auto-generated notes."
fi
- name: Release
uses: softprops/action-gh-release@v2
with:
body_path: release_notes.md
files: |
artifacts/gizmosql_cli_*.zip
artifacts/GizmoSQL-*.msi
LICENSE
update-image-manifest:
name: Merge per-platform images into multi-arch manifests (with attestations)
if: startsWith(github.ref, 'refs/tags/')
needs: build-project-linux
runs-on: ubuntu-latest
steps:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v4
with:
registry: docker.io
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Login to GitHub Container Registry
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Download per-platform image digests
uses: actions/download-artifact@v7
with:
path: /tmp/digests
pattern: digests-*
# Assemble a multi-arch manifest per (channel Γ— variant Γ— registry) from
# the per-platform digests. Because each per-platform build pushed an
# identical image (by digest) to BOTH registries, the same sha256 resolves
# in either registry β€” so we reference repo@sha256:<digest> per registry.
# `imagetools create` flattens the per-platform OCI indexes (which carry
# the SLSA provenance + SBOM attestations) into the final manifest list,
# preserving the attestations as referrers.
- name: Create and push multi-arch manifests
env:
REF_NAME: ${{ github.ref_name }}
run: |
set -euo pipefail
for channel in stable lts; do
if [ "$channel" = "lts" ]; then
dockerhub_repo="${{ env.DOCKERHUB_LTS_IMAGE_NAME }}"
ghcr_repo="${{ env.GITHUB_LTS_IMAGE_NAME }}"
else
dockerhub_repo="${{ env.DOCKERHUB_IMAGE_NAME }}"
ghcr_repo="${{ env.GITHUB_IMAGE_NAME }}"
fi
for variant in full slim; do
if [ "$variant" = "slim" ]; then sfx="-slim"; else sfx=""; fi
# Collect the per-platform digests for this channel+variant. Each
# uploaded artifact directory contains one empty file named after
# the digest (sans the "sha256:" prefix).
digests=()
for dir in /tmp/digests/digests-${channel}-${variant}-*; do
[ -d "$dir" ] || continue
for f in "$dir"/*; do
digests+=("$(basename "$f")")
done
done
if [ ${#digests[@]} -eq 0 ]; then
echo "ERROR: no digests found for ${channel}/${variant}" >&2
exit 1
fi
for repo in "$dockerhub_repo" "$ghcr_repo"; do
sources=()
for d in "${digests[@]}"; do
sources+=("${repo}@sha256:${d}")
done
echo "Creating ${repo}:latest${sfx} and ${repo}:${REF_NAME}${sfx} from ${sources[*]}"
docker buildx imagetools create \
-t "${repo}:latest${sfx}" \
-t "${repo}:${REF_NAME}${sfx}" \
"${sources[@]}"
done
done
done
update-homebrew-tap:
name: Update Homebrew tap (stable + LTS formulas)
if: startsWith(github.ref, 'refs/tags/')
needs: create-release
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Download all CLI artifacts
uses: actions/download-artifact@v8
with:
path: artifacts
pattern: gizmosql_cli_*.zip
merge-multiple: true
- name: Calculate SHA256 checksums (both channels)
id: checksums
run: |
# Stable
MACOS_ARM64_SHA=$(sha256sum artifacts/gizmosql_cli_macos_arm64.zip | cut -d ' ' -f 1)
LINUX_ARM64_SHA=$(sha256sum artifacts/gizmosql_cli_linux_arm64.zip | cut -d ' ' -f 1)
LINUX_AMD64_SHA=$(sha256sum artifacts/gizmosql_cli_linux_amd64.zip | cut -d ' ' -f 1)
echo "macos_arm64_sha=${MACOS_ARM64_SHA}" >> $GITHUB_OUTPUT
echo "linux_arm64_sha=${LINUX_ARM64_SHA}" >> $GITHUB_OUTPUT
echo "linux_amd64_sha=${LINUX_AMD64_SHA}" >> $GITHUB_OUTPUT
# LTS
MACOS_ARM64_SHA_LTS=$(sha256sum artifacts/gizmosql_cli_macos_arm64_lts.zip | cut -d ' ' -f 1)
LINUX_ARM64_SHA_LTS=$(sha256sum artifacts/gizmosql_cli_linux_arm64_lts.zip | cut -d ' ' -f 1)
LINUX_AMD64_SHA_LTS=$(sha256sum artifacts/gizmosql_cli_linux_amd64_lts.zip | cut -d ' ' -f 1)
echo "macos_arm64_sha_lts=${MACOS_ARM64_SHA_LTS}" >> $GITHUB_OUTPUT
echo "linux_arm64_sha_lts=${LINUX_ARM64_SHA_LTS}" >> $GITHUB_OUTPUT
echo "linux_amd64_sha_lts=${LINUX_AMD64_SHA_LTS}" >> $GITHUB_OUTPUT
- name: Checkout homebrew-tap
uses: actions/checkout@v6
with:
repository: gizmodata/homebrew-tap
token: ${{ secrets.HOMEBREW_TAP_TOKEN }}
path: homebrew-tap
- name: Update stable formula (gizmosql.rb)
env:
VERSION: ${{ github.ref_name }}
MACOS_ARM64_SHA: ${{ steps.checksums.outputs.macos_arm64_sha }}
LINUX_ARM64_SHA: ${{ steps.checksums.outputs.linux_arm64_sha }}
LINUX_AMD64_SHA: ${{ steps.checksums.outputs.linux_amd64_sha }}
run: |
VERSION_NUMBER=${VERSION#v}
cat > homebrew-tap/Formula/gizmosql.rb << 'FORMULA_EOF'
# typed: false
# frozen_string_literal: true
class Gizmosql < Formula
desc "High-performance SQL server built on DuckDB/SQLite with Arrow Flight SQL"
homepage "https://github.com/gizmodata/gizmosql"
version "VERSION_PLACEHOLDER"
license "Apache-2.0"
on_macos do
if Hardware::CPU.arm?
url "https://github.com/gizmodata/gizmosql/releases/download/vVERSION_PLACEHOLDER/gizmosql_cli_macos_arm64.zip"
sha256 "MACOS_ARM64_SHA_PLACEHOLDER"
end
end
on_linux do
if Hardware::CPU.arm?
url "https://github.com/gizmodata/gizmosql/releases/download/vVERSION_PLACEHOLDER/gizmosql_cli_linux_arm64.zip"
sha256 "LINUX_ARM64_SHA_PLACEHOLDER"
elsif Hardware::CPU.intel?
url "https://github.com/gizmodata/gizmosql/releases/download/vVERSION_PLACEHOLDER/gizmosql_cli_linux_amd64.zip"
sha256 "LINUX_AMD64_SHA_PLACEHOLDER"
end
end
def install
bin.install "gizmosql_server"
bin.install "gizmosql_client"
end
def caveats
<<~EOS
Start a GizmoSQL server:
GIZMOSQL_PASSWORD=tiger gizmosql_server --database-filename my_database.duckdb --username scott
Connect with the interactive SQL shell:
GIZMOSQL_PASSWORD=tiger gizmosql_client --host localhost --username scott
Run a single query:
GIZMOSQL_PASSWORD=tiger gizmosql_client --host localhost --username scott --command "SELECT 1"
For TLS and authentication options, see:
gizmosql_server --help
gizmosql_client --help
EOS
end
test do
assert_match "gizmosql_server", shell_output("#{bin}/gizmosql_server --help 2>&1", 1)
end
end
FORMULA_EOF
sed -i "s/VERSION_PLACEHOLDER/${VERSION_NUMBER}/g" homebrew-tap/Formula/gizmosql.rb
sed -i "s/MACOS_ARM64_SHA_PLACEHOLDER/${MACOS_ARM64_SHA}/g" homebrew-tap/Formula/gizmosql.rb
sed -i "s/LINUX_ARM64_SHA_PLACEHOLDER/${LINUX_ARM64_SHA}/g" homebrew-tap/Formula/gizmosql.rb
sed -i "s/LINUX_AMD64_SHA_PLACEHOLDER/${LINUX_AMD64_SHA}/g" homebrew-tap/Formula/gizmosql.rb
- name: Update LTS formula (gizmosql-lts.rb)
env:
VERSION: ${{ github.ref_name }}
MACOS_ARM64_SHA: ${{ steps.checksums.outputs.macos_arm64_sha_lts }}
LINUX_ARM64_SHA: ${{ steps.checksums.outputs.linux_arm64_sha_lts }}
LINUX_AMD64_SHA: ${{ steps.checksums.outputs.linux_amd64_sha_lts }}
run: |
VERSION_NUMBER=${VERSION#v}
cat > homebrew-tap/Formula/gizmosql-lts.rb << 'FORMULA_EOF'
# typed: false
# frozen_string_literal: true
# GizmoSQL LTS β€” built against the DuckDB LTS release.
# Installs as gizmosql_server_lts / gizmosql_client_lts so it can
# coexist with the regular `gizmosql` formula side-by-side.
class GizmosqlLts < Formula
desc "GizmoSQL (LTS channel) β€” Flight SQL server on the DuckDB LTS release"
homepage "https://github.com/gizmodata/gizmosql"
version "VERSION_PLACEHOLDER"
license "Apache-2.0"
on_macos do
if Hardware::CPU.arm?
url "https://github.com/gizmodata/gizmosql/releases/download/vVERSION_PLACEHOLDER/gizmosql_cli_macos_arm64_lts.zip"
sha256 "MACOS_ARM64_SHA_PLACEHOLDER"
end
end
on_linux do
if Hardware::CPU.arm?
url "https://github.com/gizmodata/gizmosql/releases/download/vVERSION_PLACEHOLDER/gizmosql_cli_linux_arm64_lts.zip"
sha256 "LINUX_ARM64_SHA_PLACEHOLDER"
elsif Hardware::CPU.intel?
url "https://github.com/gizmodata/gizmosql/releases/download/vVERSION_PLACEHOLDER/gizmosql_cli_linux_amd64_lts.zip"
sha256 "LINUX_AMD64_SHA_PLACEHOLDER"
end
end
def install
bin.install "gizmosql_server_lts"
bin.install "gizmosql_client_lts"
end
def caveats
<<~EOS
LTS channel binaries install as gizmosql_server_lts and gizmosql_client_lts.
Start a GizmoSQL LTS server:
GIZMOSQL_PASSWORD=tiger gizmosql_server_lts --database-filename my_database.duckdb --username scott
Connect with the interactive SQL shell:
GIZMOSQL_PASSWORD=tiger gizmosql_client_lts --host localhost --username scott
For TLS and authentication options, see:
gizmosql_server_lts --help
gizmosql_client_lts --help
EOS
end
test do
assert_match "gizmosql_server", shell_output("#{bin}/gizmosql_server_lts --help 2>&1", 1)
end
end
FORMULA_EOF
sed -i "s/VERSION_PLACEHOLDER/${VERSION_NUMBER}/g" homebrew-tap/Formula/gizmosql-lts.rb
sed -i "s/MACOS_ARM64_SHA_PLACEHOLDER/${MACOS_ARM64_SHA}/g" homebrew-tap/Formula/gizmosql-lts.rb
sed -i "s/LINUX_ARM64_SHA_PLACEHOLDER/${LINUX_ARM64_SHA}/g" homebrew-tap/Formula/gizmosql-lts.rb
sed -i "s/LINUX_AMD64_SHA_PLACEHOLDER/${LINUX_AMD64_SHA}/g" homebrew-tap/Formula/gizmosql-lts.rb
- name: Commit and push (single commit, both formulas)
working-directory: homebrew-tap
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add Formula/gizmosql.rb Formula/gizmosql-lts.rb
git commit -m "Update gizmosql + gizmosql-lts to ${{ github.ref_name }}"
git push
trigger-gizmosql-py-release:
name: Trigger gizmosql-py release (PyPI)
if: startsWith(github.ref, 'refs/tags/v')
needs: create-release
runs-on: ubuntu-latest
steps:
- name: Fire repository_dispatch on gizmosql-py
# Sends an `upstream-release` event to gizmodata/gizmosql-py with
# this tag's version. That repo's sync-version.yml workflow listens
# for it, bumps src/gizmosql/_version.py, tags v<X.Y.Z>, and pushes
# β€” which triggers gizmosql-py's own publish workflow and lands a
# matching release on PyPI. End result: every GizmoSQL release ships
# a same-versioned `pip install gizmosql` automatically.
env:
GH_TOKEN: ${{ secrets.GIZMOSQL_PY_DISPATCH_TOKEN }}
run: |
set -euo pipefail
gh api repos/gizmodata/gizmosql-py/dispatches \
-X POST \
-f event_type=upstream-release \
-f "client_payload[version]=${GITHUB_REF_NAME}"
echo "βœ“ Dispatched upstream-release(${GITHUB_REF_NAME}) to gizmosql-py"
build-ios:
name: Build & Sign iOS App
# macos-26 runner image ships Xcode 26 / iOS 26 SDK. Apple requires
# all App Store Connect uploads (and TestFlight builds) to be built
# with the iOS 26 SDK starting 2026-04-28; older runners (macos-14
# with Xcode 16.2 / iOS 18.2 SDK) trigger ITMS-90725 warnings and
# will be rejected after the cutoff.
runs-on: macos-26
timeout-minutes: 120
# NOTE: Currently runs on every push to exercise the iOS build + signing
# pipeline before we have a tagged release that legitimately needs it.
# Once stable, gate on tags/manual dispatch by adding:
# if: startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch'
env:
# Do not pin DEVELOPER_DIR here β€” it would override setup-xcode's
# selection. We export DEVELOPER_DIR from the "Select Xcode" step.
SCHEME: GizmoSQL
CONFIGURATION: Release
ARCHIVE_PATH: ios/GizmoSQL/build/GizmoSQL.xcarchive
EXPORT_PATH: ios/GizmoSQL/build/export
steps:
- name: Checkout
uses: actions/checkout@v6
with:
submodules: recursive
- name: Select Xcode
uses: maxim-lobanov/setup-xcode@v1
with:
# latest-stable on the macos-26 runner resolves to Xcode 26.x,
# which ships the iOS 26 SDK (required for App Store Connect
# uploads from 2026-04-28 onwards β€” see ITMS-90725).
xcode-version: latest-stable
- name: Export DEVELOPER_DIR
run: |
DEV_DIR="$(xcode-select -p)"
echo "Selected Xcode: $DEV_DIR"
echo "DEVELOPER_DIR=$DEV_DIR" >> "$GITHUB_ENV"
xcodebuild -version
- name: Install host build tools
env:
HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK: 1
run: |
brew install automake boost xcodegen ninja cmake
# Cache the heavy iOS deps build (DuckDB + arrow + extensions cross-compile).
# Key on the inputs that materially change the output.
- name: Cache iOS dependency build
id: ios-deps-cache
uses: actions/cache@v5
with:
path: |
ios/build/libs
ios/build/ios-arm64
ios/build/host-tools
ios/build/openssl-ios
ios/build/openssl-src
ios/build/croaring-build-ios
ios/build/croaring-src
# Bump the v<N> prefix to invalidate the cache after script/toolchain fixes.
key: ios-deps-v5-${{ runner.os }}-${{ hashFiles('third_party/DuckDB_CMakeLists.txt.in', 'third_party/duckdb_extensions.cmake', 'ios/scripts/build-ios-libs.sh', 'ios/cmake/**', 'ios/CMakeLists.txt') }}
restore-keys: |
ios-deps-v5-${{ runner.os }}-
- name: Build iOS static libraries
run: |
# Some macOS runner images export SDKROOT/iOS deployment target
# vars globally, which leaks into the Phase 1 host-tools build
# and makes clang default to the iPhone target. Scrub them so the
# script's own toolchain selection (macOS for Phase 1, iOS for
# Phase 2+) is authoritative.
unset SDKROOT IPHONEOS_DEPLOYMENT_TARGET TVOS_DEPLOYMENT_TARGET \
WATCHOS_DEPLOYMENT_TARGET XROS_DEPLOYMENT_TARGET
bash ios/scripts/build-ios-libs.sh
- name: Derive marketing version from tag
id: version
run: |
# If this build is for a tag like 'v1.22.1', strip the leading 'v'
# and use it as MARKETING_VERSION. Otherwise leave the project.yml
# default in place (no override).
REF="${GITHUB_REF#refs/tags/}"
if [[ "$GITHUB_REF" == refs/tags/v* ]]; then
VERSION="${REF#v}"
echo "MARKETING_VERSION=$VERSION" >> "$GITHUB_ENV"
echo "Using tag-derived MARKETING_VERSION=$VERSION"
else
echo "Not a tag build; using project.yml MARKETING_VERSION default"
fi
# Always bump the build number so TestFlight accepts re-uploads.
# github.run_number is monotonically increasing per workflow.
echo "CURRENT_PROJECT_VERSION=${{ github.run_number }}" >> "$GITHUB_ENV"
- name: Generate Xcode project
working-directory: ios/GizmoSQL
run: xcodegen generate
# ---- Code signing ----
- name: Import Apple distribution certificate
uses: apple-actions/import-codesign-certs@v7
with:
p12-file-base64: ${{ secrets.IOS_DIST_CERT_B64 }}
p12-password: ${{ secrets.IOS_DIST_CERT_PASSWORD }}
- name: Install provisioning profile
env:
PROFILE_B64: ${{ secrets.IOS_PROVISIONING_PROFILE_B64 }}
run: |
PROFILE_DIR="$HOME/Library/MobileDevice/Provisioning Profiles"
mkdir -p "$PROFILE_DIR"
echo -n "$PROFILE_B64" | base64 --decode > "$PROFILE_DIR/dist.mobileprovision"
# Extract metadata so we can build with explicit identifiers.
PLIST=$(security cms -D -i "$PROFILE_DIR/dist.mobileprovision")
PROFILE_UUID=$(/usr/libexec/PlistBuddy -c "Print :UUID" /dev/stdin <<<"$PLIST")
PROFILE_NAME=$(/usr/libexec/PlistBuddy -c "Print :Name" /dev/stdin <<<"$PLIST")
TEAM_ID=$(/usr/libexec/PlistBuddy -c "Print :TeamIdentifier:0" /dev/stdin <<<"$PLIST")
echo "PROFILE_UUID=$PROFILE_UUID" >> "$GITHUB_ENV"
echo "PROFILE_NAME=$PROFILE_NAME" >> "$GITHUB_ENV"
echo "TEAM_ID=$TEAM_ID" >> "$GITHUB_ENV"
- name: Archive (xcodebuild)
working-directory: ios/GizmoSQL
run: |
# Pass MARKETING_VERSION through only when set (tag builds);
# otherwise the project.yml default is used.
MV_ARG=()
if [[ -n "${MARKETING_VERSION:-}" ]]; then
MV_ARG=(MARKETING_VERSION="$MARKETING_VERSION")
fi
xcodebuild \
-project GizmoSQL.xcodeproj \
-scheme "$SCHEME" \
-configuration "$CONFIGURATION" \
-destination 'generic/platform=iOS' \
-archivePath build/GizmoSQL.xcarchive \
archive \
CODE_SIGN_STYLE=Manual \
DEVELOPMENT_TEAM="$TEAM_ID" \
PROVISIONING_PROFILE_SPECIFIER="$PROFILE_NAME" \
CODE_SIGN_IDENTITY="Apple Distribution" \
CURRENT_PROJECT_VERSION="$CURRENT_PROJECT_VERSION" \
"${MV_ARG[@]}" \
-allowProvisioningUpdates=NO
- name: Write ExportOptions.plist
run: |
cat > ios/GizmoSQL/build/ExportOptions.plist <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>method</key><string>app-store</string>
<key>teamID</key><string>${TEAM_ID}</string>
<key>signingStyle</key><string>manual</string>
<key>signingCertificate</key><string>Apple Distribution</string>
<key>provisioningProfiles</key>
<dict>
<key>com.gizmodata.gizmosql.ios</key>
<string>${PROFILE_NAME}</string>
</dict>
<key>uploadBitcode</key><false/>
<key>uploadSymbols</key><true/>
<key>compileBitcode</key><false/>
</dict>
</plist>
EOF
- name: Export IPA
working-directory: ios/GizmoSQL
run: |
xcodebuild \
-exportArchive \
-archivePath build/GizmoSQL.xcarchive \
-exportOptionsPlist build/ExportOptions.plist \
-exportPath build/export \
-allowProvisioningUpdates=NO
- name: Upload IPA artifact
uses: actions/upload-artifact@v7
with:
name: GizmoSQL-iOS-ipa
path: ios/GizmoSQL/build/export/*.ipa
if-no-files-found: error
retention-days: 14
# ---- TestFlight upload (only for tag pushes) ----
- name: Upload to TestFlight
if: startsWith(github.ref, 'refs/tags/v')
env:
KEY_ID: ${{ secrets.APP_STORE_CONNECT_KEY_ID }}
ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}
API_KEY_B64: ${{ secrets.APP_STORE_CONNECT_API_KEY_B64 }}
run: |
mkdir -p "$HOME/.appstoreconnect/private_keys"
echo -n "$API_KEY_B64" | base64 --decode \
> "$HOME/.appstoreconnect/private_keys/AuthKey_${KEY_ID}.p8"
IPA=$(ls ios/GizmoSQL/build/export/*.ipa | head -1)
xcrun altool --upload-app -f "$IPA" -t ios \
--apiKey "$KEY_ID" --apiIssuer "$ISSUER_ID"