test: fix MaxMetadataEnvVar.EnvVarRaisesLimit abort on slow runners #437
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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" |