Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
266 changes: 266 additions & 0 deletions .github/workflows/build-distributions.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,266 @@
name: Build Distributions

# The single owner of the wheel platform matrix, the maturin settings and the distribution smoke
# tests. Distributions builds the wheels for pull requests that touch packaging, Release
# Distributions builds and keeps every distribution for a release pull request, and Publish either
# smoke tests and reuses those distributions or builds them here from the release tag.
on:
workflow_call:
inputs:
ref:
description: "Commit, tag or branch to build; empty builds the triggering commit"
required: false
type: string
default: ""
sdist:
description: "Also build the source distributions"
required: false
type: boolean
default: true
upload:
description: "Upload the distributions and a manifest recording their source commit and tree"
required: false
type: boolean
default: false
prebuilt-run-id:
description: >-
Release Distributions run whose distributions are smoke tested and uploaded instead of
building; its manifest must record the tree of ref. The caller grants actions: read.
required: false
type: string
default: ""
retention-days:
description: "Retention of uploaded artifacts in days; 0 uses the repository default"
required: false
type: number
default: 0
outputs:
sha:
description: "Commit the distributions were built from"
value: ${{ jobs.source.outputs.sha }}
tree:
description: "Tree of the commit the distributions were built from"
value: ${{ jobs.source.outputs.tree }}

jobs:
source:
name: Resolve source
runs-on: ubuntu-24.04
outputs:
sha: ${{ steps.source.outputs.sha }}
tree: ${{ steps.source.outputs.tree }}
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.ref }}
- name: Pin the source commit
id: source
run: |
sha="$(git rev-parse HEAD)"
tree="$(git rev-parse 'HEAD^{tree}')"
echo "sha=${sha}" >> "${GITHUB_OUTPUT}"
echo "tree=${tree}" >> "${GITHUB_OUTPUT}"
echo "Distributions for commit ${sha} with tree ${tree}" >> "${GITHUB_STEP_SUMMARY}"
- name: Download the prebuilt manifest
if: inputs.prebuilt-run-id != ''
uses: actions/download-artifact@v4
with:
name: release-manifest
path: ${{ runner.temp }}/prebuilt
run-id: ${{ inputs.prebuilt-run-id }}
github-token: ${{ github.token }}
- name: Require prebuilt distributions from this exact tree
if: inputs.prebuilt-run-id != ''
env:
MANIFEST: ${{ runner.temp }}/prebuilt/release-manifest.json
PREBUILT_RUN_ID: ${{ inputs.prebuilt-run-id }}
TREE: ${{ steps.source.outputs.tree }}
run: |
recorded="$(jq -r '.tree // empty' "${MANIFEST}")"
if [[ "${recorded}" != "${TREE}" ]]; then
echo "Run ${PREBUILT_RUN_ID} built tree ${recorded:-<none>}, not ${TREE}" >&2
exit 1
fi
echo "Reusing distributions of run ${PREBUILT_RUN_ID}, built from tree ${TREE}" \
>> "${GITHUB_STEP_SUMMARY}"

wheels:
name: Build wheels (${{ matrix.platform.name }})
needs: source
runs-on: ${{ matrix.platform.runner }}
timeout-minutes: 90
strategy:
fail-fast: false
matrix:
platform:
- name: linux-x86_64
runner: ubuntu-24.04
target: x86_64
- name: linux-aarch64
runner: ubuntu-24.04-arm
target: aarch64
# DuckDB's C++ needs aligned deallocation, available from macOS 10.13.
- name: macos-x86_64
runner: macos-15-intel
target: x86_64
macos-target: "10.15"
- name: macos-arm64
runner: macos-15
target: aarch64
macos-target: "11.0"
- name: windows-x86_64
runner: windows-2025
target: x86_64
steps:
- uses: actions/checkout@v4
if: inputs.prebuilt-run-id == ''
with:
ref: ${{ needs.source.outputs.sha }}
- uses: actions/setup-python@v5
with:
python-version: "3.12"
# Both wheels compile DuckDB in (the `bundled` feature), so neither needs a system DuckDB.
- name: Set the oldest macOS the wheels support
if: matrix.platform.macos-target
shell: bash
run: echo "MACOSX_DEPLOYMENT_TARGET=${{ matrix.platform.macos-target }}" >> "$GITHUB_ENV"
- uses: PyO3/maturin-action@v1
if: inputs.prebuilt-run-id == ''
with:
command: build
args: --release --locked --out ../dist
working-directory: python
target: ${{ matrix.platform.target }}
manylinux: "2_28"
- uses: PyO3/maturin-action@v1
if: inputs.prebuilt-run-id == ''
with:
command: build
args: --release --locked --out ../../dist
working-directory: crates/sqlquack
target: ${{ matrix.platform.target }}
manylinux: "2_28"
- name: Download prebuilt wheels
if: inputs.prebuilt-run-id != ''
uses: actions/download-artifact@v4
with:
name: dist-wheel-${{ matrix.platform.name }}
path: dist
run-id: ${{ inputs.prebuilt-run-id }}
github-token: ${{ github.token }}
- name: Smoke test the installed wheels as the site's quick start runs them
shell: bash
run: |
python -m venv "${RUNNER_TEMP}/wheel-smoke"
if [[ -x "${RUNNER_TEMP}/wheel-smoke/Scripts/python.exe" ]]; then
bin="${RUNNER_TEMP}/wheel-smoke/Scripts"
else
bin="${RUNNER_TEMP}/wheel-smoke/bin"
fi
# Only the wheels just built: sqlquack-cli must resolve from dist, never from an index.
"${bin}/python" -m pip install --no-index --find-links dist sqlquack
cd "${RUNNER_TEMP}"
"${bin}/sqlquack" --version
"${bin}/python" -c "import sqlquack.api"
set +e
check="$(echo "SELECT 1 QUALIFY TRUE" | "${bin}/sqlquack" check 2>&1)"
status=$?
set -e
echo "${check}"
if [[ ${status} -ne 1 ]] || ! grep -q 'error\[SQ0203\]' <<< "${check}"; then
echo "check should refuse QUALIFY without a window function with SQ0203 and exit 1" >&2
exit 1
fi
converted="$(echo "SELECT 1 AS total" | "${bin}/sqlquack" convert --builtin-policy)"
echo "${converted}"
grep -qx 'SELECT CAST(1 AS DECIMAL(1,0)) AS "TOTAL"' <<< "${converted}"
# The README's file commands, with no sqlquack.toml in the directory: the built-in policy.
mkdir readme-smoke && cd readme-smoke
printf "SELECT customer_id, SUM(total) AS revenue\nFROM orders\nGROUP BY customer_id\n" > query.sql
printf '{"ORDERS": [["CUSTOMER_ID", "NUMBER(38,0)"], ["TOTAL", "NUMBER(12,2)"]]}\n' > schema.json
"${bin}/sqlquack" check query.sql
converted="$("${bin}/sqlquack" convert query.sql --schema schema.json)"
echo "${converted}"
grep -q 'CAST(sum("ORDERS"."TOTAL") AS DECIMAL(24,2)) AS "REVENUE"' <<< "${converted}"
# The README's Python example.
"${bin}/python" -c 'import sqlquack
result = sqlquack.convert(sql="SELECT 1 AS total", options=sqlquack.ConvertOptions(builtin_policy=True))
assert result.sql == "SELECT CAST(1 AS DECIMAL(1,0)) AS \"TOTAL\"", result.sql'
- uses: actions/upload-artifact@v4
if: inputs.upload
with:
name: dist-wheel-${{ matrix.platform.name }}
path: dist
if-no-files-found: error
retention-days: ${{ inputs.retention-days }}

sdist:
name: Build sdists
needs: source
if: inputs.sdist
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
if: inputs.prebuilt-run-id == ''
with:
ref: ${{ needs.source.outputs.sha }}
- uses: PyO3/maturin-action@v1
if: inputs.prebuilt-run-id == ''
with:
command: sdist
args: --out ../dist
working-directory: python
- uses: PyO3/maturin-action@v1
if: inputs.prebuilt-run-id == ''
with:
command: sdist
args: --out ../../dist
working-directory: crates/sqlquack
- name: Download prebuilt source distributions
if: inputs.prebuilt-run-id != ''
uses: actions/download-artifact@v4
with:
name: dist-sdist
path: dist
run-id: ${{ inputs.prebuilt-run-id }}
github-token: ${{ github.token }}
- name: Inspect source distribution contents
run: |
for archive in dist/*.tar.gz; do
tar -tzf "$archive" > "${RUNNER_TEMP}/archive-contents.txt"
if grep -E '(__pycache__/|\.pyc$|\.tmp$)' "${RUNNER_TEMP}/archive-contents.txt"; then
echo "Source distribution contains generated or temporary script files"
exit 1
fi
done
- uses: actions/upload-artifact@v4
if: inputs.upload
with:
name: dist-sdist
path: dist
if-no-files-found: error
retention-days: ${{ inputs.retention-days }}

manifest:
name: Record distribution source
needs: [source, wheels, sdist]
if: >-
${{ !cancelled() && inputs.upload && needs.wheels.result == 'success'
&& (needs.sdist.result == 'success' || needs.sdist.result == 'skipped') }}
runs-on: ubuntu-24.04
steps:
- name: Write the manifest
env:
SHA: ${{ needs.source.outputs.sha }}
TREE: ${{ needs.source.outputs.tree }}
run: |
jq -n --arg sha "${SHA}" --arg tree "${TREE}" --arg run_id "${GITHUB_RUN_ID}" \
'{sha: $sha, tree: $tree, run_id: $run_id}' \
> "${RUNNER_TEMP}/release-manifest.json"
cat "${RUNNER_TEMP}/release-manifest.json"
- uses: actions/upload-artifact@v4
with:
name: release-manifest
path: ${{ runner.temp }}/release-manifest.json
if-no-files-found: error
retention-days: ${{ inputs.retention-days }}
27 changes: 27 additions & 0 deletions .github/workflows/distributions.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: Distributions

# Builds and smoke tests every wheel and sdist when a pull request changes what goes into them.
on:
pull_request:
paths:
- "python/**"
- "crates/sqlquack/pyproject.toml"
- "crates/sqlquack-python/**"
- "Cargo.toml"
- "Cargo.lock"
- "rust-toolchain.toml"
- ".github/workflows/build-distributions.yml"
- ".github/workflows/distributions.yml"
workflow_dispatch:

permissions:
contents: read

concurrency:
group: distributions-${{ github.ref }}
cancel-in-progress: true

jobs:
distributions:
name: Distributions
uses: ./.github/workflows/build-distributions.yml
Loading
Loading