#!/usr/bin/env bash # Build qroissant wheels via Docker. Both Linux and Windows builds run inside # the qroissant-build:latest image (defined in scripts/Dockerfile.build) so the # host needs only Docker + Python. # # Usage: # scripts/build.sh image Build the qroissant-build Docker image. # scripts/build.sh linux Build a manylinux x86_64 wheel -> dist-linux/ # scripts/build.sh windows Build a win_amd64 wheel -> dist-windows/ # scripts/build.sh all image + linux + windows. # scripts/build.sh check Install the latest linux wheel into .venv # and import qroissant as a smoke test. # scripts/build.sh clean Remove dist-*/ output dirs (volumes kept). # scripts/build.sh clean-cache Also drop the Docker volumes (cargo # registry, target dir, xwin SDK cache). set -euo pipefail SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd) ROOT_DIR=$(cd -- "$SCRIPT_DIR/.." &>/dev/null && pwd) cd "$ROOT_DIR" IMAGE=qroissant-build:latest VOL_CARGO=qroissant-cargo-registry VOL_TARGET=qroissant-target VOL_XWIN=qroissant-xwin need_docker() { command -v docker >/dev/null 2>&1 || { echo "error: docker is required but not on PATH" >&2 exit 1 } } ensure_volumes() { local uid gid uid=$(id -u); gid=$(id -g) for v in "$VOL_CARGO" "$VOL_TARGET" "$VOL_XWIN"; do if ! docker volume inspect "$v" >/dev/null 2>&1; then docker volume create "$v" >/dev/null fi # Re-chown the volume to the invoking user every run; cheap and idempotent. docker run --rm -v "$v":/v --entrypoint=sh "$IMAGE" -c "chown -R $uid:$gid /v" >/dev/null done } ensure_image() { if ! docker image inspect "$IMAGE" >/dev/null 2>&1; then echo ">>> building $IMAGE" docker build -t "$IMAGE" -f "$SCRIPT_DIR/Dockerfile.build" "$SCRIPT_DIR" fi } run_in_image() { # Run as the invoking user so produced wheels are owned by them, not root. # CARGO_HOME / XDG_CACHE_HOME are redirected into the persistent volumes, # which must therefore be owned by the same uid (handled by ensure_volumes). docker run --rm \ --user "$(id -u):$(id -g)" \ -e CARGO_HOME=/cargo \ -e XDG_CACHE_HOME=/xdg-cache \ -e HOME=/tmp \ -e XWIN_ACCEPT_LICENSE=1 \ -v "$ROOT_DIR":/io -w /io \ -v "$VOL_CARGO":/cargo \ -v "$VOL_TARGET":/io/target \ -v "$VOL_XWIN":/xdg-cache/cargo-xwin \ --entrypoint=sh \ "$IMAGE" -c "$1" } cmd_image() { need_docker echo ">>> (re)building $IMAGE" docker build -t "$IMAGE" -f "$SCRIPT_DIR/Dockerfile.build" "$SCRIPT_DIR" } cmd_linux() { need_docker ensure_image ensure_volumes echo ">>> building Linux wheel -> dist-linux/" run_in_image 'maturin build --release --out /io/dist-linux' ls -1 dist-linux/*.whl } cmd_windows() { need_docker ensure_image ensure_volumes echo ">>> building Windows wheel -> dist-windows/" run_in_image ' maturin build --release --target x86_64-pc-windows-msvc --out /io/dist-windows # Copy the PDB next to the wheel when one was produced (release profile # has debug = "line-tables-only"). Place it next to _native.pyd at # runtime on Windows to get resolved panic backtraces. pdb=$(find /io/target/x86_64-pc-windows-msvc/release -maxdepth 2 -name "_native.pdb" 2>/dev/null | head -1) if [ -n "$pdb" ]; then cp "$pdb" /io/dist-windows/ echo "+ copied $(basename "$pdb") -> dist-windows/" fi ' ls -1 dist-windows/ } cmd_check() { local venv="$ROOT_DIR/.venv" if [[ ! -x "$venv/bin/python" ]]; then echo ">>> creating venv at $venv" python3 -m venv "$venv" "$venv/bin/pip" install --quiet --upgrade pip fi local wheel wheel=$(ls -t dist-linux/qroissant-*-linux*.whl dist-linux/qroissant-*-manylinux*.whl 2>/dev/null | head -1 || true) if [[ -z "$wheel" ]]; then echo "error: no Linux wheel in dist-linux/; run '$0 linux' first" >&2 exit 1 fi echo ">>> installing $wheel" "$venv/bin/pip" install --quiet --force-reinstall "$wheel" "$venv/bin/python" -c 'import qroissant; print("ok: qroissant", qroissant.__version__ if hasattr(qroissant, "__version__") else "(no __version__)")' if [[ -f dist-windows/qroissant-*-win_amd64.whl ]] 2>/dev/null || ls dist-windows/qroissant-*-win_amd64.whl >/dev/null 2>&1; then echo ">>> inspecting Windows wheel" for w in dist-windows/qroissant-*-win_amd64.whl; do python3 -m zipfile -l "$w" | grep -E '_native\.pyd|WHEEL' || true done fi } cmd_clean() { rm -rf dist-linux dist-windows echo ">>> removed dist-linux/ dist-windows/" } cmd_clean_cache() { cmd_clean for v in "$VOL_CARGO" "$VOL_TARGET" "$VOL_XWIN"; do docker volume rm "$v" >/dev/null 2>&1 && echo ">>> removed volume $v" || true done } case "${1:-}" in image) cmd_image ;; linux) cmd_linux ;; windows) cmd_windows ;; all) cmd_image; cmd_linux; cmd_windows ;; check) cmd_check ;; clean) cmd_clean ;; clean-cache) cmd_clean_cache ;; ""|-h|--help) sed -n '2,/^set -euo/p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//' | sed '/^set -euo/d' ;; *) echo "error: unknown command: $1" >&2 echo "run '$0 --help' for usage" >&2 exit 2 ;; esac