Skip to content
Draft
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
136 changes: 131 additions & 5 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ name: Go

on:
push:
branches: [main]
# TEMPORARY: mani/sea-kernel-build-recipe is here only to run CI on the
# stacked build-recipe branch (whose PR base is the feature branch, not
# main, so the pull_request trigger below won't fire for it). This entry
# MUST be removed before merge — do not ship it to main.
branches: [main, mani/sea-kernel-build-recipe]
pull_request:
branches: [main]

Expand Down Expand Up @@ -92,10 +96,8 @@ jobs:

# This matrix builds the default pure-Go driver (CGO_ENABLED=0), which does
# NOT compile the SEA-via-kernel backend (//go:build cgo && databricks_kernel).
# A dedicated CGO_ENABLED=1 `-tags databricks_kernel` test job needs the
# kernel static library linked in CI, which arrives with the kernel
# distribution work (a pinned-revision source build or a published .a).
# Until then the kernel path's cgo files are not exercised here.
# That opt-in path is exercised by the separate build-and-test-kernel job
# below, which builds the kernel static library and links it with CGO.
- name: Test
run: make test
env:
Expand All @@ -106,3 +108,127 @@ jobs:

- name: Build
run: make linux

build-and-test-kernel:
name: Test (kernel backend)
# Exercises the opt-in SEA-via-kernel backend: builds the Rust kernel static
# lib from the pinned KERNEL_REV and runs the `databricks_kernel`-tagged unit
# tests with CGO. Separate from build-and-test so that job's CGO_ENABLED=0
# pure-Go invariant is untouched. No warehouse creds here, so the live e2e /
# parity tests self-skip; only the tagged unit tests run.
runs-on:
group: databricks-protected-runner-group
labels: linux-ubuntu-latest

steps:
- name: Check out code into the Go module directory
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0

# Go module proxy (GOPROXY + ~/.netrc) via JFrog OIDC. Also exports
# JFROG_ACCESS_TOKEN, which the cargo step below reuses.
- name: Setup JFrog
uses: ./.github/actions/setup-jfrog

# The protected runner blocks direct crates.io access (go/hardened-gha),
# so point cargo at the JFrog crates proxy. This repo's setup-jfrog is
# Go-only, so configure cargo inline here reusing its JFROG_ACCESS_TOKEN.
# The token stays in ~/.cargo/credentials.toml (not a CARGO*-prefixed env
# var) so rust-cache keys stay stable across runs.
- name: Configure cargo to use JFrog
shell: bash
run: |
set -euo pipefail
mkdir -p ~/.cargo
cat > ~/.cargo/config.toml << 'EOF'
[source.crates-io]
replace-with = "jfrog"

[source.jfrog]
registry = "sparse+https://databricks.jfrog.io/artifactory/api/cargo/db-cargo-remote/index/"

[registries.jfrog]
index = "sparse+https://databricks.jfrog.io/artifactory/api/cargo/db-cargo-remote/index/"
credential-provider = ["cargo:token"]
EOF
cat > ~/.cargo/credentials.toml << EOF
[registries.jfrog]
token = "Bearer ${JFROG_ACCESS_TOKEN}"
EOF
chmod 600 ~/.cargo/credentials.toml

- name: Set up Go Toolchain
uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0
with:
go-version: '1.25.x'
cache: false

- name: Set up Rust Toolchain
uses: actions-rust-lang/setup-rust-toolchain@46268bd060767258de96ed93c1251119784f2ab6 # v1.16.1
with:
toolchain: stable

# The kernel repo (databricks/databricks-sql-kernel) is private, and the
# hardened runner has no ambient git credentials, so kernel-lib.sh's fetch
# fails with "could not read Username". Mint a repo-scoped token from the
# INTEGRATION_TEST_APP GitHub App (installed on the org with kernel read
# access — the same mechanism the ODBC driver uses) and rewrite the kernel
# HTTPS URL to carry it. This is transparent to kernel-lib.sh. Retire once
# the kernel publishes a release artifact (KERNEL_LOCAL_A / download path).
- name: Generate GitHub App token for databricks-sql-kernel
id: kernel-token
uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0
with:
app-id: ${{ secrets.INTEGRATION_TEST_APP_ID }}
private-key: ${{ secrets.INTEGRATION_TEST_PRIVATE_KEY }}
owner: databricks
repositories: databricks-sql-kernel

- name: Rewrite kernel repo URL to authenticated HTTPS
env:
TOKEN: ${{ steps.kernel-token.outputs.token }}
run: |
git config --global \
url."https://x-access-token:${TOKEN}@github.com/databricks/".insteadOf \
"https://github.com/databricks/"

# Cache the built kernel .a keyed on KERNEL_REV: rebuild only when the pin
# moves. The ~85MB archive dwarfs a rebuild trigger, so keep the key tight.
- name: Cache kernel static lib
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
internal/backend/kernel/lib
internal/backend/kernel/include
key: ${{ runner.os }}-kernellib-${{ hashFiles('KERNEL_REV') }}

# Cache the cargo registry + kernel build tree so a cache miss on the .a
# is still an incremental Rust build, not a cold ~200-crate compile.
- name: Cache cargo + kernel build tree
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.cargo/registry
~/.cargo/git
build/kernel-src/target
key: ${{ runner.os }}-kernel-cargo-${{ hashFiles('KERNEL_REV') }}
restore-keys: |
${{ runner.os }}-kernel-cargo-

# make (for the recipe) and a C compiler (cgo compiles the kernel binding
# stubs and links libdatabricks_sql_kernel.a). cc is preinstalled on the
# protected runner — the kernel repo's own c-abi job relies on the same —
# so these conditional installs are a no-op guard that keeps the job robust
# to a future image change rather than a known gap.
- name: Install build prerequisites (make, C compiler)
run: |
if ! command -v make &> /dev/null ; then
apt-get update && apt-get install -y make
fi
if ! command -v cc &> /dev/null ; then
apt-get update && apt-get install -y build-essential
fi

# make test-kernel builds the kernel lib (make kernel-lib) if the cache
# missed, then runs `CGO_ENABLED=1 go test -tags databricks_kernel ./...`.
- name: Build kernel lib + run tagged tests
run: make test-kernel
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,8 @@ _tmp*
.vscode/
__debug_bin
.DS_Store

# Kernel (SEA) backend build artifacts — produced by `make kernel-lib`, never committed.
/build/kernel-src/
/internal/backend/kernel/lib/
/internal/backend/kernel/include/
1 change: 1 addition & 0 deletions KERNEL_REV
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
9b90406
43 changes: 43 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,46 @@ build: linux darwin ## Build the multi-arch binaries
$(PLATFORMS):
mkdir -p bin
GOOS=$(os) GOARCH=amd64 $(GO) build $(GOBUILD_ARGS) -ldflags '$(LDFLAGS)' -o bin/$(BINARY)-$(os)-amd64 .

# ── Kernel (SEA) backend ──────────────────────────────────────────────────────
# Opt-in cgo build that links the Rust SQL kernel's C ABI, gated behind the
# `databricks_kernel` build tag. These targets are wholly separate from the
# pure-Go defaults above (which stay CGO_ENABLED=0 and never touch the kernel).
KERNEL_REV = $(shell cat KERNEL_REV)
KERNEL_REPO ?= https://github.com/databricks/databricks-sql-kernel.git
KERNEL_SRC ?= build/kernel-src
# Target OS/arch (honors GOOS/GOARCH overrides) vs the actual host, so the build
# step can reject a source cross-build that would drop a host .a into a foreign
# target dir. Multi-OS support is via native per-OS runners (host == target) or
# staging a prebuilt .a with KERNEL_LOCAL_A — not cross-compiling from source.
KERNEL_GOOS = $(shell go env GOOS)
KERNEL_GOARCH = $(shell go env GOARCH)
KERNEL_GOHOSTOS = $(shell go env GOHOSTOS)
KERNEL_GOHOSTARCH = $(shell go env GOHOSTARCH)
KERNEL_LIB_DIR = internal/backend/kernel/lib/$(KERNEL_GOOS)_$(KERNEL_GOARCH)
KERNEL_INC_DIR = internal/backend/kernel/include
KERNEL_GO = CGO_ENABLED=1 go
KERNEL_TAGS = -tags databricks_kernel

.PHONY: kernel-lib
kernel-lib: ## Build the pinned kernel static lib + header into the cgo link dir (source build).
KERNEL_REPO="$(KERNEL_REPO)" KERNEL_REV="$(KERNEL_REV)" KERNEL_SRC="$(KERNEL_SRC)" \
KERNEL_LIB_DIR="$(KERNEL_LIB_DIR)" KERNEL_INC_DIR="$(KERNEL_INC_DIR)" \
KERNEL_GOOS="$(KERNEL_GOOS)" KERNEL_GOARCH="$(KERNEL_GOARCH)" \
KERNEL_GOHOSTOS="$(KERNEL_GOHOSTOS)" KERNEL_GOHOSTARCH="$(KERNEL_GOHOSTARCH)" \
KERNEL_LOCAL_A="$(KERNEL_LOCAL_A)" KERNEL_LOCAL_HEADER="$(KERNEL_LOCAL_HEADER)" \
./build/kernel-lib.sh

.PHONY: build-kernel
build-kernel: kernel-lib ## Build the driver with the kernel backend linked.
$(KERNEL_GO) build $(KERNEL_TAGS) ./...

.PHONY: test-kernel
test-kernel: kernel-lib ## Run the kernel-tagged unit tests (no warehouse needed).
$(KERNEL_GO) test $(KERNEL_TAGS) ./...

.PHONY: kernel-lib-download
kernel-lib-download: ## Prod mode (download prebuilt .a): blocked until the kernel publishes release artifacts.
@echo "kernel-lib-download: blocked — the kernel does not yet publish per-platform .a release artifacts."
@echo "Use 'make kernel-lib' (source build) meanwhile. See the distribution design doc."
@false
177 changes: 177 additions & 0 deletions build/kernel-lib.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
#!/usr/bin/env bash
#
# kernel-lib.sh — build (or copy in) the Databricks SQL kernel static library
# for the cgo `databricks_kernel` backend.
#
# Mode 1 (source build, the default and the only thing that works today): clone
# the kernel at the pinned KERNEL_REV, `cargo build` a self-contained static
# archive with pure-Rust TLS, and drop the .a + C header where the per-platform
# cgo_<os>.go files link them (${SRCDIR}/lib/<os>_<arch> and ${SRCDIR}/include,
# both .gitignore'd). Kernel releases publish no binary assets yet, so building
# from a pinned commit is the current distribution model (mirrors the ODBC
# driver's Corrosion-from-source build).
#
# Local override: set KERNEL_LOCAL_A (and optionally KERNEL_LOCAL_HEADER) to
# copy an already-built archive instead of cloning + building — the analogue of
# the ODBC driver's KERNEL_LOCAL_PATH.
#
# Invoked by `make kernel-lib`, which supplies the environment below. It can
# also be run directly with the same variables set.
#
# Required env (Makefile provides these):
# KERNEL_REV kernel commit SHA to build (from the repo-root KERNEL_REV)
# KERNEL_REPO git URL of the kernel repo
# KERNEL_SRC working dir for the kernel checkout (gitignored)
# KERNEL_LIB_DIR dest dir for the .a (…/lib/<os>_<arch>)
# KERNEL_INC_DIR dest dir for the header (…/include)
# Optional env:
# KERNEL_LOCAL_A path to a prebuilt libdatabricks_sql_kernel.a to copy
# KERNEL_LOCAL_HEADER path to a databricks_kernel.h to copy (defaults to a
# header next to KERNEL_LOCAL_A, else the checkout's)

set -euo pipefail

: "${KERNEL_LIB_DIR:?KERNEL_LIB_DIR must be set (run via 'make kernel-lib')}"
: "${KERNEL_INC_DIR:?KERNEL_INC_DIR must be set (run via 'make kernel-lib')}"

LIB_NAME="libdatabricks_sql_kernel.a"
HEADER_NAME="databricks_kernel.h"

log() { printf '[kernel-lib] %s\n' "$*" >&2; }

emit_checksum() {
# A reproducibility breadcrumb: the archive path + its content hash.
local a="$KERNEL_LIB_DIR/$LIB_NAME"
if command -v sha256sum >/dev/null 2>&1; then
log "artifact: $a"
log "sha256: $(sha256sum "$a" | cut -d' ' -f1)"
elif command -v shasum >/dev/null 2>&1; then
log "artifact: $a"
log "sha256: $(shasum -a 256 "$a" | cut -d' ' -f1)"
fi
}

mkdir -p "$KERNEL_LIB_DIR" "$KERNEL_INC_DIR"

# ── Local override: copy a prebuilt archive, skip clone + cargo entirely. ──────
if [ -n "${KERNEL_LOCAL_A:-}" ]; then
[ -f "$KERNEL_LOCAL_A" ] || { log "KERNEL_LOCAL_A not found: $KERNEL_LOCAL_A"; exit 1; }
local_header="${KERNEL_LOCAL_HEADER:-}"
if [ -z "$local_header" ]; then
# Prefer a header next to the archive; fall back to the checkout's include/.
if [ -f "$(dirname "$KERNEL_LOCAL_A")/$HEADER_NAME" ]; then
local_header="$(dirname "$KERNEL_LOCAL_A")/$HEADER_NAME"
elif [ -n "${KERNEL_SRC:-}" ] && [ -f "$KERNEL_SRC/include/$HEADER_NAME" ]; then
local_header="$KERNEL_SRC/include/$HEADER_NAME"
fi
fi
[ -n "$local_header" ] && [ -f "$local_header" ] || {
log "header not found; set KERNEL_LOCAL_HEADER to a $HEADER_NAME"; exit 1; }
log "copying prebuilt archive from $KERNEL_LOCAL_A"
cp "$KERNEL_LOCAL_A" "$KERNEL_LIB_DIR/$LIB_NAME"
cp "$local_header" "$KERNEL_INC_DIR/$HEADER_NAME"
emit_checksum
exit 0
fi

# ── Mode 1: build from source at the pinned rev. ──────────────────────────────
: "${KERNEL_REV:?KERNEL_REV must be set (run via 'make kernel-lib')}"
: "${KERNEL_REPO:?KERNEL_REPO must be set (run via 'make kernel-lib')}"
: "${KERNEL_SRC:?KERNEL_SRC must be set (run via 'make kernel-lib')}"

# Cache short-circuit: if a previously built archive + header are present AND the
# rev-stamp beside the archive matches KERNEL_REV, the artifact is already what
# this pin would produce — skip the git fetch + cargo build entirely (and don't
# even require a Rust toolchain). `kernel-lib` is .PHONY and CI re-invokes it on
# every run after restoring the .a cache (keyed on KERNEL_REV), so without this
# guard the build re-ran and overwrote the just-restored archive every time.
# The stamp (not just file existence) is what makes this safe against a local
# KERNEL_REV bump with a stale on-disk .a: a changed pin fails the match and
# forces a rebuild. In CI the .a-cache key already includes KERNEL_REV, so a pin
# change misses the cache and the artifact is simply absent — either way a bump
# rebuilds.
REV_STAMP="$KERNEL_LIB_DIR/.kernel-rev"
if [ -f "$KERNEL_LIB_DIR/$LIB_NAME" ] && [ -f "$KERNEL_INC_DIR/$HEADER_NAME" ] &&
[ -f "$REV_STAMP" ] && [ "$(cat "$REV_STAMP")" = "$KERNEL_REV" ]; then
log "cache hit: $LIB_NAME already built for $KERNEL_REV — skipping source build"
emit_checksum
exit 0
fi

# Reject a source cross-build. `cargo build` below has no --target, so it emits
# the HOST triple's archive — but KERNEL_LIB_DIR is named for the TARGET
# (GOOS/GOARCH). If they differ, copying the host .a into the target dir would
# silently produce a wrong-arch artifact. Multi-OS is served by native per-OS
# runners (host == target, so this passes) or by staging a prebuilt cross-target
# .a via KERNEL_LOCAL_A (handled above, before this check) — not by cross-
# building the kernel from source, which the distribution design defers to the
# download path. Fail loud rather than mislink. Only enforced when the Makefile
# passes the host vars; a direct script call without them skips the check.
if [ -n "${KERNEL_GOOS:-}" ] && [ -n "${KERNEL_GOHOSTOS:-}" ] &&
{ [ "$KERNEL_GOOS" != "$KERNEL_GOHOSTOS" ] || [ "${KERNEL_GOARCH:-}" != "${KERNEL_GOHOSTARCH:-}" ]; }; then
log "refusing source cross-build: target ${KERNEL_GOOS}/${KERNEL_GOARCH} != host ${KERNEL_GOHOSTOS}/${KERNEL_GOHOSTARCH}."
log "build on a native ${KERNEL_GOOS} runner, or stage a prebuilt archive with KERNEL_LOCAL_A=<path>."
exit 1
fi

command -v cargo >/dev/null 2>&1 || {
log "cargo not found — the source build needs a Rust toolchain."
log "install rustup, or use 'make kernel-lib KERNEL_LOCAL_A=<path>' with a prebuilt .a."
exit 1
}

# Make $KERNEL_SRC a git repo pointed at the kernel remote, WITHOUT assuming an
# empty destination. CI caches build/kernel-src/target/ (for incremental Rust
# builds) but not .git, so on a cache hit the dir exists with a target/ subtree
# and no repo — a plain `git clone` would abort ("destination path already
# exists and is not an empty directory"). `git init` is idempotent and works
# whether the dir is absent, empty, or holds a restored target/; the kernel's
# own .gitignore excludes /target, so the later checkout leaves it untouched.
mkdir -p "$KERNEL_SRC"
if [ ! -d "$KERNEL_SRC/.git" ]; then
log "initializing git repo in $KERNEL_SRC (-> $KERNEL_REPO)"
git -C "$KERNEL_SRC" init --quiet
fi
if git -C "$KERNEL_SRC" remote get-url origin >/dev/null 2>&1; then
git -C "$KERNEL_SRC" remote set-url origin "$KERNEL_REPO"
else
git -C "$KERNEL_SRC" remote add origin "$KERNEL_REPO"
fi

log "fetching + checking out $KERNEL_REV"
git -C "$KERNEL_SRC" fetch --all --tags --quiet || true
# checkout -f: this is a tool-managed, gitignored scratch checkout of a pinned
# third-party repo, so force past any leftover files (e.g. a source tree left
# behind if .git was lost). -f overwrites tracked-path collisions but leaves the
# gitignored target/ alone, so a cache-restored build tree survives. Without -f,
# an untracked-file conflict would masquerade as "commit not reachable" and send
# us down the PR-head fallback for the wrong reason.
#
# KERNEL_REV may be a bare commit that only lives on a PR ref (e.g. #163's head
# before it merges), so try the commit directly, then the PR head ref.
if ! git -C "$KERNEL_SRC" checkout -f --quiet "$KERNEL_REV" 2>/dev/null; then
log "commit not directly reachable; trying PR head refs"
git -C "$KERNEL_SRC" fetch --quiet origin '+refs/pull/*/head:refs/remotes/origin/pr/*' || true
git -C "$KERNEL_SRC" checkout -f --quiet "$KERNEL_REV" || {
log "could not check out $KERNEL_REV — is it pushed/fetchable?"; exit 1; }
fi
log "kernel at $(git -C "$KERNEL_SRC" rev-parse --short HEAD)"

# --no-default-features --features tls-rustls: pure-Rust TLS (no system OpenSSL)
# keeps the archive self-contained and cross-compile-tractable. The kernel's
# default is tls-native, so the override is required.
log "cargo build --release --no-default-features --features tls-rustls"
( cd "$KERNEL_SRC" && cargo build --release --no-default-features --features tls-rustls )

src_a="$KERNEL_SRC/target/release/$LIB_NAME"
src_h="$KERNEL_SRC/include/$HEADER_NAME"
[ -f "$src_a" ] || { log "expected archive not produced: $src_a"; exit 1; }
[ -f "$src_h" ] || { log "expected header not found: $src_h"; exit 1; }

cp "$src_a" "$KERNEL_LIB_DIR/$LIB_NAME"
cp "$src_h" "$KERNEL_INC_DIR/$HEADER_NAME"
# Record the rev this archive was built for so a later run can short-circuit
# (see the cache short-circuit above). Written last, only after a successful
# build, so a stamp never claims an artifact that isn't there.
printf '%s\n' "$KERNEL_REV" > "$REV_STAMP"
emit_checksum
Loading