#!/usr/bin/env bash set -euo pipefail # Script to package jacquard-lexicon binaries for distribution using Nix cross-compilation # Creates tar.xz archives with binaries, man pages, completions, README, LICENSE, and config # # Generates two versions: # - Unversioned archives in binaries/ (tracked in git, overwritten each build) # - Versioned archives in binaries/releases/ (gitignored, for GitHub releases) # Determine project root (script is in scripts/ subdirectory) SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" cd "$PROJECT_ROOT" # Parse version from workspace Cargo.toml VERSION=$(grep '^version = ' Cargo.toml | head -1 | sed 's/version = "\(.*\)"/\1/') echo "Packaging version: $VERSION" # Detect current system CURRENT_SYSTEM=$(nix eval --impure --expr 'builtins.currentSystem' --raw) echo "Current system: $CURRENT_SYSTEM" # Map target triples to nix package names declare -A TARGET_TO_PACKAGE=( ["x86_64-unknown-linux-gnu"]="jacquard-lexgen-x86_64-linux" ["aarch64-unknown-linux-gnu"]="jacquard-lexgen-aarch64-linux" ["x86_64-apple-darwin"]="jacquard-lexgen-x86_64-darwin" ["aarch64-apple-darwin"]="jacquard-lexgen-aarch64-darwin" ["x86_64-pc-windows-gnu"]="jacquard-lexgen-x86_64-windows" ["aarch64-pc-windows-gnullvm"]="jacquard-lexgen-aarch64-windows" ) # Determine which targets we can build from the current system TARGETS=() case "$CURRENT_SYSTEM" in x86_64-linux) # Linux can cross-compile to other Linux archs and Windows x86_64 # macOS requires remote builders or actual macOS hardware # aarch64-windows: nixpkgs mingw-w64-pthreads broken for aarch64 (missing winver.h) TARGETS=( "x86_64-unknown-linux-gnu" "aarch64-unknown-linux-gnu" "x86_64-pc-windows-gnu" # "aarch64-pc-windows-gnullvm" # TODO: nixpkgs cross-compile broken ) echo "Building from x86_64-linux: Linux (x86_64, aarch64) + Windows (x86_64)" echo "Note: macOS cross-compilation requires remote builders or macOS hardware" echo "Note: aarch64-windows cross-compilation broken in nixpkgs (mingw-w64-pthreads build fails)" ;; aarch64-linux) # Linux can cross-compile to other Linux archs and Windows x86_64 # macOS requires remote builders or actual macOS hardware # aarch64-windows: nixpkgs mingw-w64-pthreads broken for aarch64 (missing winver.h) TARGETS=( "aarch64-unknown-linux-gnu" "x86_64-unknown-linux-gnu" "x86_64-pc-windows-gnu" # "aarch64-pc-windows-gnullvm" # TODO: nixpkgs cross-compile broken ) echo "Building from aarch64-linux: Linux (aarch64, x86_64) + Windows (x86_64)" echo "Note: macOS cross-compilation requires remote builders or macOS hardware" echo "Note: aarch64-windows cross-compilation broken in nixpkgs (mingw-w64-pthreads build fails)" ;; x86_64-darwin) # macOS cross-compilation is limited TARGETS=( "x86_64-apple-darwin" ) echo "Building from x86_64-darwin: x86_64-darwin only" ;; aarch64-darwin) # macOS aarch64 can build both macOS targets via rosetta TARGETS=( "aarch64-apple-darwin" "x86_64-apple-darwin" ) echo "Building from aarch64-darwin: macOS targets (aarch64 + x86_64 via rosetta)" ;; *) echo "Error: Unknown system: $CURRENT_SYSTEM" echo "This script supports: x86_64-linux, aarch64-linux, x86_64-darwin, aarch64-darwin" exit 1 ;; esac echo "" echo "Will build for: ${TARGETS[*]}" echo "" # Output directories OUTPUT_DIR="binaries" RELEASES_DIR="binaries/releases" mkdir -p "$OUTPUT_DIR" mkdir -p "$RELEASES_DIR" # Helper function to package for a target package_target() { local TARGET=$1 local PACKAGE_NAME="${TARGET_TO_PACKAGE[$TARGET]}" echo "" echo "======================================" echo "Building for $TARGET" echo "======================================" # Build with nix using cross-compilation package echo "Running: nix build .#${PACKAGE_NAME}" if ! nix build ".#${PACKAGE_NAME}" -o "result-${TARGET}"; then echo "Error: nix build failed for $TARGET" return 1 fi # Package each binary separately for BINARY in lex-fetch jacquard-codegen; do echo " Packaging binary: $BINARY" package_binary "$TARGET" "$BINARY" done # Cleanup rm -f "$PROJECT_ROOT/result-${TARGET}" cd "$PROJECT_ROOT" } # Helper function to package a single binary package_binary() { local TARGET=$1 local BINARY=$2 # Determine binary extension local BINARY_EXT="" if [[ "$TARGET" == *"windows"* ]]; then BINARY_EXT=".exe" fi # Check if binary exists if [[ ! -f "result-${TARGET}/bin/${BINARY}${BINARY_EXT}" ]]; then echo " Warning: ${BINARY}${BINARY_EXT} not found, skipping" return 0 fi # Names for versioned and unversioned archives local VERSIONED_NAME="${BINARY}_${TARGET}_v${VERSION}" local UNVERSIONED_NAME="${BINARY}_${TARGET}" # Create staging directory local STAGE_DIR="/tmp/${VERSIONED_NAME}" rm -rf "$STAGE_DIR" mkdir -p "$STAGE_DIR" # Detect if this is a Windows target if [[ "$TARGET" == *"windows"* ]]; then # Windows: binary, README, LICENSE, example config (for lex-fetch only) cp "result-${TARGET}/bin/${BINARY}.exe" "$STAGE_DIR/" cp LICENSE "$STAGE_DIR/" cp README.md "$STAGE_DIR/" # Only include example config for lex-fetch if [[ "$BINARY" == "lex-fetch" ]]; then mkdir -p "$STAGE_DIR/examples" cp crates/jacquard-lexgen/lexicons.kdl.example "$STAGE_DIR/examples/" 2>/dev/null || true fi else # Unix (Linux/macOS): binary, man page, completions, README, LICENSE mkdir -p "$STAGE_DIR/bin" cp "result-${TARGET}/bin/${BINARY}" "$STAGE_DIR/bin/" cp LICENSE "$STAGE_DIR/" cp README.md "$STAGE_DIR/" # Copy man page if it exists if [[ -f "result-${TARGET}/share/man/man1/${BINARY}.1.gz" ]]; then mkdir -p "$STAGE_DIR/share/man/man1" cp "result-${TARGET}/share/man/man1/${BINARY}.1.gz" "$STAGE_DIR/share/man/man1/" fi # Copy completions if they exist for shell_dir in bash fish zsh; do local comp_dir="result-${TARGET}/share/$shell_dir/site-functions" if [[ "$shell_dir" == "bash" ]]; then comp_dir="result-${TARGET}/share/bash-completion/completions" elif [[ "$shell_dir" == "fish" ]]; then comp_dir="result-${TARGET}/share/fish/vendor_completions.d" fi if [[ -d "$comp_dir" ]]; then for comp in "$comp_dir"/*; do local comp_name=$(basename "$comp") # Only copy completions for this specific binary if [[ "$comp_name" == "${BINARY}"* ]] || [[ "$comp_name" == "_${BINARY}" ]]; then mkdir -p "$STAGE_DIR/share/$(dirname "${comp#result-${TARGET}/share/}")" cp "$comp" "$STAGE_DIR/share/$(dirname "${comp#result-${TARGET}/share/}")/" fi done fi done # Only include example config for lex-fetch if [[ "$BINARY" == "lex-fetch" ]]; then mkdir -p "$STAGE_DIR/share/doc/jacquard-lexicon" cp crates/jacquard-lexgen/lexicons.kdl.example "$STAGE_DIR/share/doc/jacquard-lexgen/" 2>/dev/null || true fi fi # Create versioned archive (for releases) cd /tmp # Use .zip for Windows, .tar.xz for Unix if [[ "$TARGET" == *"windows"* ]]; then zip -r "${VERSIONED_NAME}.zip" "$VERSIONED_NAME" mv "${VERSIONED_NAME}.zip" "$PROJECT_ROOT/$RELEASES_DIR/" echo " Created: ${RELEASES_DIR}/${VERSIONED_NAME}.zip" # Rename and create unversioned archive mv "$VERSIONED_NAME" "$UNVERSIONED_NAME" zip -r "${UNVERSIONED_NAME}.zip" "$UNVERSIONED_NAME" mv "${UNVERSIONED_NAME}.zip" "$PROJECT_ROOT/$OUTPUT_DIR/" echo " Created: ${OUTPUT_DIR}/${UNVERSIONED_NAME}.zip" else tar -cJf "${VERSIONED_NAME}.tar.xz" "$VERSIONED_NAME" mv "${VERSIONED_NAME}.tar.xz" "$PROJECT_ROOT/$RELEASES_DIR/" echo " Created: ${RELEASES_DIR}/${VERSIONED_NAME}.tar.xz" # Rename and create unversioned archive mv "$VERSIONED_NAME" "$UNVERSIONED_NAME" tar -cJf "${UNVERSIONED_NAME}.tar.xz" "$UNVERSIONED_NAME" mv "${UNVERSIONED_NAME}.tar.xz" "$PROJECT_ROOT/$OUTPUT_DIR/" echo " Created: ${OUTPUT_DIR}/${UNVERSIONED_NAME}.tar.xz" fi # Cleanup rm -rf "$UNVERSIONED_NAME" # Return to project root cd "$PROJECT_ROOT" } # Build for all targets for target in "${TARGETS[@]}"; do package_target "$target" || echo "Warning: build failed for $target, continuing..." done # Print summary echo "" echo "Packaging complete!" echo "" echo "Tracked archives (binaries/):" ls -lh "$OUTPUT_DIR"/*.tar.xz 2>/dev/null || true ls -lh "$OUTPUT_DIR"/*.zip 2>/dev/null || true echo "" echo "Release archives (binaries/releases/):" ls -lh "$RELEASES_DIR"/*.tar.xz 2>/dev/null || true ls -lh "$RELEASES_DIR"/*.zip 2>/dev/null || true # Generate checksums for tracked archives echo "" echo "Generating checksums for tracked archives..." cd "$OUTPUT_DIR" sha256sum *.tar.xz *.zip 2>/dev/null > SHA256SUMS || true echo "Checksums written to ${OUTPUT_DIR}/SHA256SUMS" cat SHA256SUMS # Generate checksums for release archives echo "" echo "Generating checksums for release archives..." cd "$PROJECT_ROOT/$RELEASES_DIR" sha256sum *.tar.xz *.zip 2>/dev/null > SHA256SUMS || true echo "Checksums written to ${RELEASES_DIR}/SHA256SUMS" cat SHA256SUMS