From 9b799ab1642692266d6bdcc770a6a655e7f8ef1a Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Sat, 11 Jul 2026 13:16:02 +0300 Subject: [PATCH] feat(release): cross-compilation scripts, minimal release profile and .qext distribution packaging --- .github/workflows/release.yml | 80 +++++++++++++++++++++++ .gitignore | 1 + Cargo.toml | 1 + scripts/build_cross.sh | 81 +++++++++++++++++++++++ scripts/package_qext.sh | 119 ++++++++++++++++++++++++++++++++++ 5 files changed, 282 insertions(+) create mode 100644 .github/workflows/release.yml create mode 100755 scripts/build_cross.sh create mode 100755 scripts/package_qext.sh diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..cd0ba71 --- /dev/null +++ b/.github/workflows/release.yml @@ -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 diff --git a/.gitignore b/.gitignore index 934b926..f8c654b 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ *.log /scratch/ .env +/dist/ diff --git a/Cargo.toml b/Cargo.toml index a547a79..d35d01b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,3 +37,4 @@ lto = true strip = true opt-level = 3 codegen-units = 1 +panic = "abort" diff --git a/scripts/build_cross.sh b/scripts/build_cross.sh new file mode 100755 index 0000000..d1bee8e --- /dev/null +++ b/scripts/build_cross.sh @@ -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!" diff --git a/scripts/package_qext.sh b/scripts/package_qext.sh new file mode 100755 index 0000000..24f9689 --- /dev/null +++ b/scripts/package_qext.sh @@ -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 '." + 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."