|
| 1 | +#!/usr/bin/env bash |
| 2 | +# |
| 3 | +# kernel-lib.sh — build (or copy in) the Databricks SQL kernel static library |
| 4 | +# for the cgo `databricks_kernel` backend. |
| 5 | +# |
| 6 | +# Mode 1 (source build, the default and the only thing that works today): clone |
| 7 | +# the kernel at the pinned KERNEL_REV, `cargo build` a self-contained static |
| 8 | +# archive with pure-Rust TLS, and drop the .a + C header where the per-platform |
| 9 | +# cgo_<os>.go files link them (${SRCDIR}/lib/<os>_<arch> and ${SRCDIR}/include, |
| 10 | +# both .gitignore'd). Kernel releases publish no binary assets yet, so building |
| 11 | +# from a pinned commit is the current distribution model (mirrors the ODBC |
| 12 | +# driver's Corrosion-from-source build). |
| 13 | +# |
| 14 | +# Local override: set KERNEL_LOCAL_A (and optionally KERNEL_LOCAL_HEADER) to |
| 15 | +# copy an already-built archive instead of cloning + building — the analogue of |
| 16 | +# the ODBC driver's KERNEL_LOCAL_PATH. |
| 17 | +# |
| 18 | +# Invoked by `make kernel-lib`, which supplies the environment below. It can |
| 19 | +# also be run directly with the same variables set. |
| 20 | +# |
| 21 | +# Required env (Makefile provides these): |
| 22 | +# KERNEL_REV kernel commit SHA to build (from the repo-root KERNEL_REV) |
| 23 | +# KERNEL_REPO git URL of the kernel repo |
| 24 | +# KERNEL_SRC working dir for the kernel checkout (gitignored) |
| 25 | +# KERNEL_LIB_DIR dest dir for the .a (…/lib/<os>_<arch>) |
| 26 | +# KERNEL_INC_DIR dest dir for the header (…/include) |
| 27 | +# Optional env: |
| 28 | +# KERNEL_LOCAL_A path to a prebuilt libdatabricks_sql_kernel.a to copy |
| 29 | +# KERNEL_LOCAL_HEADER path to a databricks_kernel.h to copy (defaults to a |
| 30 | +# header next to KERNEL_LOCAL_A, else the checkout's) |
| 31 | + |
| 32 | +set -euo pipefail |
| 33 | + |
| 34 | +: "${KERNEL_LIB_DIR:?KERNEL_LIB_DIR must be set (run via 'make kernel-lib')}" |
| 35 | +: "${KERNEL_INC_DIR:?KERNEL_INC_DIR must be set (run via 'make kernel-lib')}" |
| 36 | + |
| 37 | +LIB_NAME="libdatabricks_sql_kernel.a" |
| 38 | +HEADER_NAME="databricks_kernel.h" |
| 39 | + |
| 40 | +log() { printf '[kernel-lib] %s\n' "$*" >&2; } |
| 41 | + |
| 42 | +emit_checksum() { |
| 43 | + # A reproducibility breadcrumb: the archive path + its content hash. |
| 44 | + local a="$KERNEL_LIB_DIR/$LIB_NAME" |
| 45 | + if command -v sha256sum >/dev/null 2>&1; then |
| 46 | + log "artifact: $a" |
| 47 | + log "sha256: $(sha256sum "$a" | cut -d' ' -f1)" |
| 48 | + elif command -v shasum >/dev/null 2>&1; then |
| 49 | + log "artifact: $a" |
| 50 | + log "sha256: $(shasum -a 256 "$a" | cut -d' ' -f1)" |
| 51 | + fi |
| 52 | +} |
| 53 | + |
| 54 | +mkdir -p "$KERNEL_LIB_DIR" "$KERNEL_INC_DIR" |
| 55 | + |
| 56 | +# ── Local override: copy a prebuilt archive, skip clone + cargo entirely. ────── |
| 57 | +if [ -n "${KERNEL_LOCAL_A:-}" ]; then |
| 58 | + [ -f "$KERNEL_LOCAL_A" ] || { log "KERNEL_LOCAL_A not found: $KERNEL_LOCAL_A"; exit 1; } |
| 59 | + local_header="${KERNEL_LOCAL_HEADER:-}" |
| 60 | + if [ -z "$local_header" ]; then |
| 61 | + # Prefer a header next to the archive; fall back to the checkout's include/. |
| 62 | + if [ -f "$(dirname "$KERNEL_LOCAL_A")/$HEADER_NAME" ]; then |
| 63 | + local_header="$(dirname "$KERNEL_LOCAL_A")/$HEADER_NAME" |
| 64 | + elif [ -n "${KERNEL_SRC:-}" ] && [ -f "$KERNEL_SRC/include/$HEADER_NAME" ]; then |
| 65 | + local_header="$KERNEL_SRC/include/$HEADER_NAME" |
| 66 | + fi |
| 67 | + fi |
| 68 | + [ -n "$local_header" ] && [ -f "$local_header" ] || { |
| 69 | + log "header not found; set KERNEL_LOCAL_HEADER to a $HEADER_NAME"; exit 1; } |
| 70 | + log "copying prebuilt archive from $KERNEL_LOCAL_A" |
| 71 | + cp "$KERNEL_LOCAL_A" "$KERNEL_LIB_DIR/$LIB_NAME" |
| 72 | + cp "$local_header" "$KERNEL_INC_DIR/$HEADER_NAME" |
| 73 | + emit_checksum |
| 74 | + exit 0 |
| 75 | +fi |
| 76 | + |
| 77 | +# ── Mode 1: build from source at the pinned rev. ────────────────────────────── |
| 78 | +: "${KERNEL_REV:?KERNEL_REV must be set (run via 'make kernel-lib')}" |
| 79 | +: "${KERNEL_REPO:?KERNEL_REPO must be set (run via 'make kernel-lib')}" |
| 80 | +: "${KERNEL_SRC:?KERNEL_SRC must be set (run via 'make kernel-lib')}" |
| 81 | + |
| 82 | +# Cache short-circuit: if a previously built archive + header are present AND the |
| 83 | +# rev-stamp beside the archive matches KERNEL_REV, the artifact is already what |
| 84 | +# this pin would produce — skip the git fetch + cargo build entirely (and don't |
| 85 | +# even require a Rust toolchain). `kernel-lib` is .PHONY and CI re-invokes it on |
| 86 | +# every run after restoring the .a cache (keyed on KERNEL_REV), so without this |
| 87 | +# guard the build re-ran and overwrote the just-restored archive every time. |
| 88 | +# The stamp (not just file existence) is what makes this safe against a local |
| 89 | +# KERNEL_REV bump with a stale on-disk .a: a changed pin fails the match and |
| 90 | +# forces a rebuild. In CI the .a-cache key already includes KERNEL_REV, so a pin |
| 91 | +# change misses the cache and the artifact is simply absent — either way a bump |
| 92 | +# rebuilds. |
| 93 | +REV_STAMP="$KERNEL_LIB_DIR/.kernel-rev" |
| 94 | +if [ -f "$KERNEL_LIB_DIR/$LIB_NAME" ] && [ -f "$KERNEL_INC_DIR/$HEADER_NAME" ] && |
| 95 | + [ -f "$REV_STAMP" ] && [ "$(cat "$REV_STAMP")" = "$KERNEL_REV" ]; then |
| 96 | + log "cache hit: $LIB_NAME already built for $KERNEL_REV — skipping source build" |
| 97 | + emit_checksum |
| 98 | + exit 0 |
| 99 | +fi |
| 100 | + |
| 101 | +# Reject a source cross-build. `cargo build` below has no --target, so it emits |
| 102 | +# the HOST triple's archive — but KERNEL_LIB_DIR is named for the TARGET |
| 103 | +# (GOOS/GOARCH). If they differ, copying the host .a into the target dir would |
| 104 | +# silently produce a wrong-arch artifact. Multi-OS is served by native per-OS |
| 105 | +# runners (host == target, so this passes) or by staging a prebuilt cross-target |
| 106 | +# .a via KERNEL_LOCAL_A (handled above, before this check) — not by cross- |
| 107 | +# building the kernel from source, which the distribution design defers to the |
| 108 | +# download path. Fail loud rather than mislink. Only enforced when the Makefile |
| 109 | +# passes the host vars; a direct script call without them skips the check. |
| 110 | +if [ -n "${KERNEL_GOOS:-}" ] && [ -n "${KERNEL_GOHOSTOS:-}" ] && |
| 111 | + { [ "$KERNEL_GOOS" != "$KERNEL_GOHOSTOS" ] || [ "${KERNEL_GOARCH:-}" != "${KERNEL_GOHOSTARCH:-}" ]; }; then |
| 112 | + log "refusing source cross-build: target ${KERNEL_GOOS}/${KERNEL_GOARCH} != host ${KERNEL_GOHOSTOS}/${KERNEL_GOHOSTARCH}." |
| 113 | + log "build on a native ${KERNEL_GOOS} runner, or stage a prebuilt archive with KERNEL_LOCAL_A=<path>." |
| 114 | + exit 1 |
| 115 | +fi |
| 116 | + |
| 117 | +command -v cargo >/dev/null 2>&1 || { |
| 118 | + log "cargo not found — the source build needs a Rust toolchain." |
| 119 | + log "install rustup, or use 'make kernel-lib KERNEL_LOCAL_A=<path>' with a prebuilt .a." |
| 120 | + exit 1 |
| 121 | +} |
| 122 | + |
| 123 | +# Make $KERNEL_SRC a git repo pointed at the kernel remote, WITHOUT assuming an |
| 124 | +# empty destination. CI caches build/kernel-src/target/ (for incremental Rust |
| 125 | +# builds) but not .git, so on a cache hit the dir exists with a target/ subtree |
| 126 | +# and no repo — a plain `git clone` would abort ("destination path already |
| 127 | +# exists and is not an empty directory"). `git init` is idempotent and works |
| 128 | +# whether the dir is absent, empty, or holds a restored target/; the kernel's |
| 129 | +# own .gitignore excludes /target, so the later checkout leaves it untouched. |
| 130 | +mkdir -p "$KERNEL_SRC" |
| 131 | +if [ ! -d "$KERNEL_SRC/.git" ]; then |
| 132 | + log "initializing git repo in $KERNEL_SRC (-> $KERNEL_REPO)" |
| 133 | + git -C "$KERNEL_SRC" init --quiet |
| 134 | +fi |
| 135 | +if git -C "$KERNEL_SRC" remote get-url origin >/dev/null 2>&1; then |
| 136 | + git -C "$KERNEL_SRC" remote set-url origin "$KERNEL_REPO" |
| 137 | +else |
| 138 | + git -C "$KERNEL_SRC" remote add origin "$KERNEL_REPO" |
| 139 | +fi |
| 140 | + |
| 141 | +log "fetching + checking out $KERNEL_REV" |
| 142 | +git -C "$KERNEL_SRC" fetch --all --tags --quiet || true |
| 143 | +# checkout -f: this is a tool-managed, gitignored scratch checkout of a pinned |
| 144 | +# third-party repo, so force past any leftover files (e.g. a source tree left |
| 145 | +# behind if .git was lost). -f overwrites tracked-path collisions but leaves the |
| 146 | +# gitignored target/ alone, so a cache-restored build tree survives. Without -f, |
| 147 | +# an untracked-file conflict would masquerade as "commit not reachable" and send |
| 148 | +# us down the PR-head fallback for the wrong reason. |
| 149 | +# |
| 150 | +# KERNEL_REV may be a bare commit that only lives on a PR ref (e.g. #163's head |
| 151 | +# before it merges), so try the commit directly, then the PR head ref. |
| 152 | +if ! git -C "$KERNEL_SRC" checkout -f --quiet "$KERNEL_REV" 2>/dev/null; then |
| 153 | + log "commit not directly reachable; trying PR head refs" |
| 154 | + git -C "$KERNEL_SRC" fetch --quiet origin '+refs/pull/*/head:refs/remotes/origin/pr/*' || true |
| 155 | + git -C "$KERNEL_SRC" checkout -f --quiet "$KERNEL_REV" || { |
| 156 | + log "could not check out $KERNEL_REV — is it pushed/fetchable?"; exit 1; } |
| 157 | +fi |
| 158 | +log "kernel at $(git -C "$KERNEL_SRC" rev-parse --short HEAD)" |
| 159 | + |
| 160 | +# --no-default-features --features tls-rustls: pure-Rust TLS (no system OpenSSL) |
| 161 | +# keeps the archive self-contained and cross-compile-tractable. The kernel's |
| 162 | +# default is tls-native, so the override is required. |
| 163 | +log "cargo build --release --no-default-features --features tls-rustls" |
| 164 | +( cd "$KERNEL_SRC" && cargo build --release --no-default-features --features tls-rustls ) |
| 165 | + |
| 166 | +src_a="$KERNEL_SRC/target/release/$LIB_NAME" |
| 167 | +src_h="$KERNEL_SRC/include/$HEADER_NAME" |
| 168 | +[ -f "$src_a" ] || { log "expected archive not produced: $src_a"; exit 1; } |
| 169 | +[ -f "$src_h" ] || { log "expected header not found: $src_h"; exit 1; } |
| 170 | + |
| 171 | +cp "$src_a" "$KERNEL_LIB_DIR/$LIB_NAME" |
| 172 | +cp "$src_h" "$KERNEL_INC_DIR/$HEADER_NAME" |
| 173 | +# Record the rev this archive was built for so a later run can short-circuit |
| 174 | +# (see the cache short-circuit above). Written last, only after a successful |
| 175 | +# build, so a stamp never claims an artifact that isn't there. |
| 176 | +printf '%s\n' "$KERNEL_REV" > "$REV_STAMP" |
| 177 | +emit_checksum |
0 commit comments