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
80 changes: 80 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
name: Build & Release Extension (.qext)

on:
push:
tags:
- 'v*'
workflow_dispatch:

env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: 1

jobs:
build-release:
name: Build & Package (${{ matrix.target }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- target: x86_64-unknown-linux-gnu
os: ubuntu-latest
- target: aarch64-apple-darwin
os: macos-14
- target: x86_64-pc-windows-msvc
os: windows-latest

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
targets: ${{ matrix.target }}

- name: Cache Cargo registry and target directory
uses: Swatinem/rust-cache@v2

- name: Build release binary for target
run: cargo build --release --target ${{ matrix.target }}

- name: Package extension into .qext archive
shell: bash
run: |
chmod +x scripts/package_qext.sh
./scripts/package_qext.sh --target ${{ matrix.target }}
ls -lh dist/

- name: Upload .qext and .sha256 artifacts
uses: actions/upload-artifact@v4
with:
name: clickhouse-query-ext-${{ matrix.target }}
path: |
dist/*.qext
dist/*.sha256
if-no-files-found: error

create-github-release:
name: Create GitHub Release
needs: build-release
if: startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Download all release artifacts
uses: actions/download-artifact@v4
with:
path: dist-all/
merge-multiple: true

- name: Create Release and Upload .qext archives
uses: softprops/action-gh-release@v2
with:
files: |
dist-all/*.qext
dist-all/*.sha256
generate_release_notes: true
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
*.log
/scratch/
.env
/dist/
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,4 @@ lto = true
strip = true
opt-level = 3
codegen-units = 1
panic = "abort"
81 changes: 81 additions & 0 deletions scripts/build_cross.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
#!/usr/bin/env bash
set -eo pipefail

# Script to cross-compile clickhouse-query-ext for the 3 supported Querya Desktop targets:
# - x86_64-unknown-linux-gnu (Linux x86_64)
# - aarch64-apple-darwin (macOS Apple Silicon)
# - x86_64-pc-windows-msvc / x86_64-pc-windows-gnu (Windows x86_64)

TARGETS=(
"x86_64-unknown-linux-gnu"
"aarch64-apple-darwin"
"x86_64-pc-windows-msvc"
)

# Determine the native host target
HOST_TARGET=$(rustc -vV | sed -n 's|host: ||p')
BUILD_CMD="cargo"

# Check if cross is installed and requested, or use cargo directly
if command -v cross &> /dev/null && [ "$USE_CROSS" = "1" ]; then
BUILD_CMD="cross"
fi

echo "=========================================================================="
echo "🎯 Starting release build / cross-compilation for clickhouse-query-ext..."
echo " Build command: ${BUILD_CMD}"
echo " Host target: ${HOST_TARGET}"
echo "=========================================================================="

# If arguments are passed, use them as target list; otherwise build either requested target or all available
if [ "$#" -gt 0 ]; then
SELECTED_TARGETS=("$@")
else
# If running locally without cross, build only the host target by default unless --all is passed
if [ "$BUILD_CMD" = "cargo" ] && [ "$1" != "--all" ]; then
echo "ℹ️ Running on local host without 'cross' CLI specified. Building for native target: ${HOST_TARGET}"
SELECTED_TARGETS=("$HOST_TARGET")
else
SELECTED_TARGETS=("${TARGETS[@]}")
fi
fi

for TARGET in "${SELECTED_TARGETS[@]}"; do
if [ "$TARGET" = "--all" ]; then
continue
fi

echo ""
echo ">>> 🔨 Building target: ${TARGET} ..."

# Check if target is installed when using standard cargo
if [ "$BUILD_CMD" = "cargo" ] && [ "$TARGET" != "$HOST_TARGET" ]; then
if ! rustup target list | grep "${TARGET} (installed)" > /dev/null; then
echo "⚠️ Target ${TARGET} not installed in rustup toolchain. Attempting to install..."
rustup target add "${TARGET}" || {
echo "❌ Failed to add target ${TARGET}. Skipping or please install target toolchain / 'cross'."
continue
}
fi
fi

# Execute build
"${BUILD_CMD}" build --release --target "${TARGET}"

# Verify output binary
BIN_NAME="clickhouse-query-ext"
if [[ "${TARGET}" == *"windows"* ]]; then
BIN_NAME="clickhouse-query-ext.exe"
fi

OUT_PATH="target/${TARGET}/release/${BIN_NAME}"
if [ -f "${OUT_PATH}" ]; then
SIZE=$(ls -lh "${OUT_PATH}" | awk '{print $5}')
echo "✅ Successfully built ${OUT_PATH} (Size: ${SIZE})"
else
echo "⚠️ Warning: expected binary ${OUT_PATH} not found after build."
fi
done

echo ""
echo "🎉 Cross-compilation process completed successfully!"
119 changes: 119 additions & 0 deletions scripts/package_qext.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
#!/usr/bin/env bash
set -eo pipefail

# Script to package clickhouse-query-ext binary along with manifest.json and assets/
# into a distribution .qext archive (.zip) and generate SHA-256 checksums for Querya Desktop Block B validation.

TARGET=""
BIN_PATH=""

while [[ "$#" -gt 0 ]]; do
case $1 in
--target) TARGET="$2"; shift 2 ;;
--bin) BIN_PATH="$2"; shift 2 ;;
*) echo "Unknown parameter passed: $1"; exit 1 ;;
esac
done

if [ -z "$TARGET" ]; then
TARGET=$(rustc -vV | sed -n 's|host: ||p' || echo "x86_64-unknown-linux-gnu")
fi

if [ -z "$BIN_PATH" ]; then
# Try target-specific path first, then fallback to target/release/
if [[ "$TARGET" == *"windows"* ]]; then
BIN_NAME="clickhouse-query-ext.exe"
DEST_BIN_NAME="clickhouse_rpc_server.exe"
else
BIN_NAME="clickhouse-query-ext"
DEST_BIN_NAME="clickhouse_rpc_server"
fi

if [ -f "target/${TARGET}/release/${BIN_NAME}" ]; then
BIN_PATH="target/${TARGET}/release/${BIN_NAME}"
elif [ -f "target/release/${BIN_NAME}" ]; then
BIN_PATH="target/release/${BIN_NAME}"
else
echo "❌ Error: Could not find built binary for ${TARGET}."
echo " Please run './scripts/build_cross.sh ${TARGET}' first or specify '--bin <path>'."
exit 1
fi
else
if [[ "$TARGET" == *"windows"* ]] || [[ "$BIN_PATH" == *".exe" ]]; then
DEST_BIN_NAME="clickhouse_rpc_server.exe"
else
DEST_BIN_NAME="clickhouse_rpc_server"
fi
fi

# Extract version from manifest.json using Python (works reliably across platforms)
VERSION=$(python3 -c "import json; print(json.load(open('manifest.json'))['version'])" 2>/dev/null || echo "1.0.0")

echo "=========================================================================="
echo "📦 Packaging Querya Extension (.qext) for target: ${TARGET}"
echo " Binary source: ${BIN_PATH}"
echo " Extension v: ${VERSION}"
echo "=========================================================================="

mkdir -p dist
STAGING_DIR="dist/staging-${TARGET}"
rm -rf "${STAGING_DIR}"
mkdir -p "${STAGING_DIR}/bin" "${STAGING_DIR}/assets"

# 1. Copy manifest and assets
cp manifest.json "${STAGING_DIR}/"
cp -r assets/* "${STAGING_DIR}/assets/"

# 2. Copy binary into bin/ under both the manifest main entry and original name
cp "${BIN_PATH}" "${STAGING_DIR}/bin/${DEST_BIN_NAME}"
if [ "${DEST_BIN_NAME}" != "${BIN_NAME}" ] && [ ! -f "${STAGING_DIR}/bin/${BIN_NAME}" ]; then
cp "${BIN_PATH}" "${STAGING_DIR}/bin/${BIN_NAME}"
fi

# 3. Create .qext (.zip) archive using python zipfile module or zip CLI
ARCHIVE_NAME="clickhouse-query-ext-${VERSION}-${TARGET}.qext"
ARCHIVE_PATH="dist/${ARCHIVE_NAME}"
rm -f "${ARCHIVE_PATH}"

python3 -c "
import zipfile, os, sys
archive_path = sys.argv[1]
staging_dir = sys.argv[2]
with zipfile.ZipFile(archive_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
for root, dirs, files in os.walk(staging_dir):
for file in files:
full_path = os.path.join(root, file)
rel_path = os.path.relpath(full_path, staging_dir)
zipf.write(full_path, rel_path)
" "${ARCHIVE_PATH}" "${STAGING_DIR}"

# Also create universal/shorthand name if packaging host target
HOST_TARGET=$(rustc -vV | sed -n 's|host: ||p' 2>/dev/null || echo "")
if [ "$TARGET" = "$HOST_TARGET" ]; then
cp "${ARCHIVE_PATH}" "dist/clickhouse-query-ext-${VERSION}.qext"
fi

# 4. Generate SHA-256 checksums
generate_sha256() {
local file_path="$1"
local sha_output="${file_path}.sha256"
python3 -c "
import hashlib, sys, os
with open(sys.argv[1], 'rb') as f:
print(hashlib.sha256(f.read()).hexdigest() + ' *' + os.path.basename(sys.argv[1]))
" "${file_path}" > "${sha_output}"
echo "🔒 Checksum generated: ${sha_output} -> $(cat "${sha_output}")"
}

generate_sha256 "${ARCHIVE_PATH}"
if [ -f "dist/clickhouse-query-ext-${VERSION}.qext" ] && [ "$TARGET" = "$HOST_TARGET" ]; then
generate_sha256 "dist/clickhouse-query-ext-${VERSION}.qext"
fi

rm -rf "${STAGING_DIR}"

SIZE=$(ls -lh "${ARCHIVE_PATH}" | awk '{print $5}')
echo ""
echo "✅ Packaging successfully completed!"
echo " Created archive: ${ARCHIVE_PATH} (Size: ${SIZE})"
echo " Ready for deployment and Block B security verification."
Loading