Skip to content

Commit fc45ca1

Browse files
committed
Merge build/pip-cli-distributions (#157): ship the sqlquack command with pip install sqlquack
2 parents dce9700 + dcbadd1 commit fc45ca1

22 files changed

Lines changed: 1472 additions & 5 deletions
Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
name: Build Distributions
2+
3+
# The single owner of the wheel platform matrix, the maturin settings and the distribution smoke
4+
# tests. Distributions builds the wheels for pull requests that touch packaging, Release
5+
# Distributions builds and keeps every distribution for a release pull request, and Publish either
6+
# smoke tests and reuses those distributions or builds them here from the release tag.
7+
on:
8+
workflow_call:
9+
inputs:
10+
ref:
11+
description: "Commit, tag or branch to build; empty builds the triggering commit"
12+
required: false
13+
type: string
14+
default: ""
15+
sdist:
16+
description: "Also build the source distributions"
17+
required: false
18+
type: boolean
19+
default: true
20+
upload:
21+
description: "Upload the distributions and a manifest recording their source commit and tree"
22+
required: false
23+
type: boolean
24+
default: false
25+
prebuilt-run-id:
26+
description: >-
27+
Release Distributions run whose distributions are smoke tested and uploaded instead of
28+
building; its manifest must record the tree of ref. The caller grants actions: read.
29+
required: false
30+
type: string
31+
default: ""
32+
retention-days:
33+
description: "Retention of uploaded artifacts in days; 0 uses the repository default"
34+
required: false
35+
type: number
36+
default: 0
37+
outputs:
38+
sha:
39+
description: "Commit the distributions were built from"
40+
value: ${{ jobs.source.outputs.sha }}
41+
tree:
42+
description: "Tree of the commit the distributions were built from"
43+
value: ${{ jobs.source.outputs.tree }}
44+
45+
jobs:
46+
source:
47+
name: Resolve source
48+
runs-on: ubuntu-24.04
49+
outputs:
50+
sha: ${{ steps.source.outputs.sha }}
51+
tree: ${{ steps.source.outputs.tree }}
52+
steps:
53+
- uses: actions/checkout@v4
54+
with:
55+
ref: ${{ inputs.ref }}
56+
- name: Pin the source commit
57+
id: source
58+
run: |
59+
sha="$(git rev-parse HEAD)"
60+
tree="$(git rev-parse 'HEAD^{tree}')"
61+
echo "sha=${sha}" >> "${GITHUB_OUTPUT}"
62+
echo "tree=${tree}" >> "${GITHUB_OUTPUT}"
63+
echo "Distributions for commit ${sha} with tree ${tree}" >> "${GITHUB_STEP_SUMMARY}"
64+
- name: Download the prebuilt manifest
65+
if: inputs.prebuilt-run-id != ''
66+
uses: actions/download-artifact@v4
67+
with:
68+
name: release-manifest
69+
path: ${{ runner.temp }}/prebuilt
70+
run-id: ${{ inputs.prebuilt-run-id }}
71+
github-token: ${{ github.token }}
72+
- name: Require prebuilt distributions from this exact tree
73+
if: inputs.prebuilt-run-id != ''
74+
env:
75+
MANIFEST: ${{ runner.temp }}/prebuilt/release-manifest.json
76+
PREBUILT_RUN_ID: ${{ inputs.prebuilt-run-id }}
77+
TREE: ${{ steps.source.outputs.tree }}
78+
run: |
79+
recorded="$(jq -r '.tree // empty' "${MANIFEST}")"
80+
if [[ "${recorded}" != "${TREE}" ]]; then
81+
echo "Run ${PREBUILT_RUN_ID} built tree ${recorded:-<none>}, not ${TREE}" >&2
82+
exit 1
83+
fi
84+
echo "Reusing distributions of run ${PREBUILT_RUN_ID}, built from tree ${TREE}" \
85+
>> "${GITHUB_STEP_SUMMARY}"
86+
87+
wheels:
88+
name: Build wheels (${{ matrix.platform.name }})
89+
needs: source
90+
runs-on: ${{ matrix.platform.runner }}
91+
timeout-minutes: 90
92+
strategy:
93+
fail-fast: false
94+
matrix:
95+
platform:
96+
- name: linux-x86_64
97+
runner: ubuntu-24.04
98+
target: x86_64
99+
- name: linux-aarch64
100+
runner: ubuntu-24.04-arm
101+
target: aarch64
102+
# DuckDB's C++ needs aligned deallocation, available from macOS 10.13.
103+
- name: macos-x86_64
104+
runner: macos-15-intel
105+
target: x86_64
106+
macos-target: "10.15"
107+
- name: macos-arm64
108+
runner: macos-15
109+
target: aarch64
110+
macos-target: "11.0"
111+
- name: windows-x86_64
112+
runner: windows-2025
113+
target: x86_64
114+
steps:
115+
- uses: actions/checkout@v4
116+
if: inputs.prebuilt-run-id == ''
117+
with:
118+
ref: ${{ needs.source.outputs.sha }}
119+
- uses: actions/setup-python@v5
120+
with:
121+
python-version: "3.12"
122+
# Both wheels compile DuckDB in (the `bundled` feature), so neither needs a system DuckDB.
123+
- name: Set the oldest macOS the wheels support
124+
if: matrix.platform.macos-target
125+
shell: bash
126+
run: echo "MACOSX_DEPLOYMENT_TARGET=${{ matrix.platform.macos-target }}" >> "$GITHUB_ENV"
127+
- uses: PyO3/maturin-action@v1
128+
if: inputs.prebuilt-run-id == ''
129+
with:
130+
command: build
131+
args: --release --locked --out ../dist
132+
working-directory: python
133+
target: ${{ matrix.platform.target }}
134+
manylinux: "2_28"
135+
- uses: PyO3/maturin-action@v1
136+
if: inputs.prebuilt-run-id == ''
137+
with:
138+
command: build
139+
args: --release --locked --out ../../dist
140+
working-directory: crates/sqlquack
141+
target: ${{ matrix.platform.target }}
142+
manylinux: "2_28"
143+
- name: Download prebuilt wheels
144+
if: inputs.prebuilt-run-id != ''
145+
uses: actions/download-artifact@v4
146+
with:
147+
name: dist-wheel-${{ matrix.platform.name }}
148+
path: dist
149+
run-id: ${{ inputs.prebuilt-run-id }}
150+
github-token: ${{ github.token }}
151+
- name: Smoke test the installed wheels as the site's quick start runs them
152+
shell: bash
153+
run: |
154+
python -m venv "${RUNNER_TEMP}/wheel-smoke"
155+
if [[ -x "${RUNNER_TEMP}/wheel-smoke/Scripts/python.exe" ]]; then
156+
bin="${RUNNER_TEMP}/wheel-smoke/Scripts"
157+
else
158+
bin="${RUNNER_TEMP}/wheel-smoke/bin"
159+
fi
160+
# Only the wheels just built: sqlquack-cli must resolve from dist, never from an index.
161+
"${bin}/python" -m pip install --no-index --find-links dist sqlquack
162+
cd "${RUNNER_TEMP}"
163+
"${bin}/sqlquack" --version
164+
"${bin}/python" -c "import sqlquack.api"
165+
set +e
166+
check="$(echo "SELECT 1 QUALIFY TRUE" | "${bin}/sqlquack" check 2>&1)"
167+
status=$?
168+
set -e
169+
echo "${check}"
170+
if [[ ${status} -ne 1 ]] || ! grep -q 'error\[SQ0203\]' <<< "${check}"; then
171+
echo "check should refuse QUALIFY without a window function with SQ0203 and exit 1" >&2
172+
exit 1
173+
fi
174+
converted="$(echo "SELECT 1 AS total" | "${bin}/sqlquack" convert --builtin-policy)"
175+
echo "${converted}"
176+
grep -qx 'SELECT CAST(1 AS DECIMAL(1,0)) AS "TOTAL"' <<< "${converted}"
177+
# The README's file commands, with no sqlquack.toml in the directory: the built-in policy.
178+
mkdir readme-smoke && cd readme-smoke
179+
printf "SELECT customer_id, SUM(total) AS revenue\nFROM orders\nGROUP BY customer_id\n" > query.sql
180+
printf '{"ORDERS": [["CUSTOMER_ID", "NUMBER(38,0)"], ["TOTAL", "NUMBER(12,2)"]]}\n' > schema.json
181+
"${bin}/sqlquack" check query.sql
182+
converted="$("${bin}/sqlquack" convert query.sql --schema schema.json)"
183+
echo "${converted}"
184+
grep -q 'CAST(sum("ORDERS"."TOTAL") AS DECIMAL(24,2)) AS "REVENUE"' <<< "${converted}"
185+
# The README's Python example.
186+
"${bin}/python" -c 'import sqlquack
187+
result = sqlquack.convert(sql="SELECT 1 AS total", options=sqlquack.ConvertOptions(builtin_policy=True))
188+
assert result.sql == "SELECT CAST(1 AS DECIMAL(1,0)) AS \"TOTAL\"", result.sql'
189+
- uses: actions/upload-artifact@v4
190+
if: inputs.upload
191+
with:
192+
name: dist-wheel-${{ matrix.platform.name }}
193+
path: dist
194+
if-no-files-found: error
195+
retention-days: ${{ inputs.retention-days }}
196+
197+
sdist:
198+
name: Build sdists
199+
needs: source
200+
if: inputs.sdist
201+
runs-on: ubuntu-24.04
202+
steps:
203+
- uses: actions/checkout@v4
204+
if: inputs.prebuilt-run-id == ''
205+
with:
206+
ref: ${{ needs.source.outputs.sha }}
207+
- uses: PyO3/maturin-action@v1
208+
if: inputs.prebuilt-run-id == ''
209+
with:
210+
command: sdist
211+
args: --out ../dist
212+
working-directory: python
213+
- uses: PyO3/maturin-action@v1
214+
if: inputs.prebuilt-run-id == ''
215+
with:
216+
command: sdist
217+
args: --out ../../dist
218+
working-directory: crates/sqlquack
219+
- name: Download prebuilt source distributions
220+
if: inputs.prebuilt-run-id != ''
221+
uses: actions/download-artifact@v4
222+
with:
223+
name: dist-sdist
224+
path: dist
225+
run-id: ${{ inputs.prebuilt-run-id }}
226+
github-token: ${{ github.token }}
227+
- name: Inspect source distribution contents
228+
run: |
229+
for archive in dist/*.tar.gz; do
230+
tar -tzf "$archive" > "${RUNNER_TEMP}/archive-contents.txt"
231+
if grep -E '(__pycache__/|\.pyc$|\.tmp$)' "${RUNNER_TEMP}/archive-contents.txt"; then
232+
echo "Source distribution contains generated or temporary script files"
233+
exit 1
234+
fi
235+
done
236+
- uses: actions/upload-artifact@v4
237+
if: inputs.upload
238+
with:
239+
name: dist-sdist
240+
path: dist
241+
if-no-files-found: error
242+
retention-days: ${{ inputs.retention-days }}
243+
244+
manifest:
245+
name: Record distribution source
246+
needs: [source, wheels, sdist]
247+
if: >-
248+
${{ !cancelled() && inputs.upload && needs.wheels.result == 'success'
249+
&& (needs.sdist.result == 'success' || needs.sdist.result == 'skipped') }}
250+
runs-on: ubuntu-24.04
251+
steps:
252+
- name: Write the manifest
253+
env:
254+
SHA: ${{ needs.source.outputs.sha }}
255+
TREE: ${{ needs.source.outputs.tree }}
256+
run: |
257+
jq -n --arg sha "${SHA}" --arg tree "${TREE}" --arg run_id "${GITHUB_RUN_ID}" \
258+
'{sha: $sha, tree: $tree, run_id: $run_id}' \
259+
> "${RUNNER_TEMP}/release-manifest.json"
260+
cat "${RUNNER_TEMP}/release-manifest.json"
261+
- uses: actions/upload-artifact@v4
262+
with:
263+
name: release-manifest
264+
path: ${{ runner.temp }}/release-manifest.json
265+
if-no-files-found: error
266+
retention-days: ${{ inputs.retention-days }}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
name: Distributions
2+
3+
# Builds and smoke tests every wheel and sdist when a pull request changes what goes into them.
4+
on:
5+
pull_request:
6+
paths:
7+
- "python/**"
8+
- "crates/sqlquack/pyproject.toml"
9+
- "crates/sqlquack-python/**"
10+
- "Cargo.toml"
11+
- "Cargo.lock"
12+
- "rust-toolchain.toml"
13+
- ".github/workflows/build-distributions.yml"
14+
- ".github/workflows/distributions.yml"
15+
workflow_dispatch:
16+
17+
permissions:
18+
contents: read
19+
20+
concurrency:
21+
group: distributions-${{ github.ref }}
22+
cancel-in-progress: true
23+
24+
jobs:
25+
distributions:
26+
name: Distributions
27+
uses: ./.github/workflows/build-distributions.yml

0 commit comments

Comments
 (0)