A better Rust ATProto crate

cross-compiling

Orual c6309d57 966ebdf0

Changed files
+247 -62
crates
jacquard-lexicon
nix
modules
scripts
-3
crates/jacquard-lexicon/lexicons.kdl.example
··· 11 11 12 12 // Path to Cargo.toml for feature generation (optional) 13 13 // cargo-toml "Cargo.toml" 14 - 15 - // NOTE: root-module option is currently disabled due to issues when set to non-"crate" values 16 - // It will always use "crate" as the root module name 17 14 } 18 15 19 16 // Fetch ATProto and Bluesky lexicons from official repo
+113
nix/modules/cross.nix
··· 1 + {inputs, ...}: { 2 + imports = [inputs.rust-flake.flakeModules.nixpkgs]; 3 + 4 + perSystem = {pkgs, lib, config, system, ...}: let 5 + # Get the filtered source from rust-project 6 + src = config.rust-project.src; 7 + 8 + # Helper to create a cross-compiled package 9 + mkCrossPackage = { 10 + crossSystem, 11 + rustTarget, 12 + extraArgs ? {} 13 + }: let 14 + # Import nixpkgs with cross-compilation configured 15 + pkgs-cross = import inputs.nixpkgs { 16 + inherit crossSystem; 17 + localSystem = system; 18 + overlays = [(import inputs.rust-overlay)]; 19 + }; 20 + 21 + # Set up crane with rust-overlay toolchain for the target 22 + craneLib = (inputs.crane.mkLib pkgs-cross).overrideToolchain (p: 23 + p.rust-bin.stable.latest.default.override { 24 + targets = [rustTarget]; 25 + } 26 + ); 27 + 28 + # Common crane args 29 + commonArgs = { 30 + inherit src; 31 + pname = "jacquard-lexicon"; 32 + strictDeps = true; 33 + doCheck = false; # Tests require lexicon corpus files 34 + 35 + # Native build inputs (tools that run during build) 36 + nativeBuildInputs = with pkgs; [ 37 + installShellFiles 38 + ]; 39 + 40 + postInstall = '' 41 + # Install man pages and completions from build script output 42 + for outdir in target/${rustTarget}/release/build/jacquard-lexicon-*/out; do 43 + if [ -d "$outdir/man" ]; then 44 + installManPage $outdir/man/*.1 45 + fi 46 + if [ -d "$outdir/completions" ]; then 47 + for completion in $outdir/completions/*; do 48 + case "$(basename "$completion")" in 49 + *.bash) installShellCompletion --bash "$completion" ;; 50 + *.fish) installShellCompletion --fish "$completion" ;; 51 + _*) installShellCompletion --zsh "$completion" ;; 52 + esac 53 + done 54 + fi 55 + done 56 + 57 + # Install example lexicons.kdl config 58 + install -Dm644 ${./../../crates/jacquard-lexicon/lexicons.kdl.example} $out/share/doc/jacquard-lexicon/lexicons.kdl.example 59 + ''; 60 + } // extraArgs; 61 + in 62 + craneLib.buildPackage commonArgs; 63 + in { 64 + packages = { 65 + # Linux targets 66 + jacquard-lexicon-x86_64-linux = mkCrossPackage { 67 + crossSystem = { 68 + config = "x86_64-unknown-linux-gnu"; 69 + }; 70 + rustTarget = "x86_64-unknown-linux-gnu"; 71 + }; 72 + 73 + jacquard-lexicon-aarch64-linux = mkCrossPackage { 74 + crossSystem = { 75 + config = "aarch64-unknown-linux-gnu"; 76 + }; 77 + rustTarget = "aarch64-unknown-linux-gnu"; 78 + }; 79 + 80 + # macOS targets 81 + jacquard-lexicon-x86_64-darwin = mkCrossPackage { 82 + crossSystem = { 83 + config = "x86_64-apple-darwin"; 84 + }; 85 + rustTarget = "x86_64-apple-darwin"; 86 + }; 87 + 88 + jacquard-lexicon-aarch64-darwin = mkCrossPackage { 89 + crossSystem = { 90 + config = "aarch64-apple-darwin"; 91 + }; 92 + rustTarget = "aarch64-apple-darwin"; 93 + }; 94 + 95 + # Windows targets 96 + jacquard-lexicon-x86_64-windows = mkCrossPackage { 97 + crossSystem = { 98 + config = "x86_64-w64-mingw32"; 99 + libc = "msvcrt"; 100 + }; 101 + rustTarget = "x86_64-pc-windows-gnu"; 102 + }; 103 + 104 + jacquard-lexicon-aarch64-windows = mkCrossPackage { 105 + crossSystem = { 106 + config = "aarch64-w64-mingw32"; 107 + libc = "msvcrt"; 108 + }; 109 + rustTarget = "aarch64-pc-windows-gnullvm"; 110 + }; 111 + }; 112 + }; 113 + }
+134 -59
scripts/package-binaries.sh
··· 1 1 #!/usr/bin/env bash 2 2 set -euo pipefail 3 3 4 - # Script to package jacquard-codegen and lex-fetch binaries for distribution 5 - # Creates tar.xz archives with binaries, README, LICENSE, and config files 4 + # Script to package jacquard-lexicon binaries for distribution using Nix cross-compilation 5 + # Creates tar.xz archives with binaries, man pages, completions, README, LICENSE, and config 6 6 # 7 7 # Generates two versions: 8 8 # - Unversioned archives in binaries/ (tracked in git, overwritten each build) ··· 17 17 VERSION=$(grep '^version = ' Cargo.toml | head -1 | sed 's/version = "\(.*\)"/\1/') 18 18 echo "Packaging version: $VERSION" 19 19 20 - # Detect target triple (default to x86_64-unknown-linux-gnu) 21 - TARGET="${CARGO_BUILD_TARGET:-x86_64-unknown-linux-gnu}" 22 - echo "Target: $TARGET" 20 + # Detect current system 21 + CURRENT_SYSTEM=$(nix eval --impure --expr 'builtins.currentSystem' --raw) 22 + echo "Current system: $CURRENT_SYSTEM" 23 + 24 + # Map target triples to nix package names and friendly names 25 + declare -A TARGET_TO_PACKAGE=( 26 + ["x86_64-unknown-linux-gnu"]="jacquard-lexicon-x86_64-linux" 27 + ["aarch64-unknown-linux-gnu"]="jacquard-lexicon-aarch64-linux" 28 + ["x86_64-apple-darwin"]="jacquard-lexicon-x86_64-darwin" 29 + ["aarch64-apple-darwin"]="jacquard-lexicon-aarch64-darwin" 30 + ["x86_64-pc-windows-gnu"]="jacquard-lexicon-x86_64-windows" 31 + ["aarch64-pc-windows-gnullvm"]="jacquard-lexicon-aarch64-windows" 32 + ) 33 + 34 + # Determine which targets we can build from the current system 35 + TARGETS=() 36 + case "$CURRENT_SYSTEM" in 37 + x86_64-linux) 38 + # Linux can cross-compile to everything 39 + TARGETS=( 40 + "x86_64-unknown-linux-gnu" 41 + "aarch64-unknown-linux-gnu" 42 + "x86_64-apple-darwin" 43 + "aarch64-apple-darwin" 44 + "x86_64-pc-windows-gnu" 45 + "aarch64-pc-windows-gnullvm" 46 + ) 47 + echo "Building from x86_64-linux: All targets (Linux, macOS, Windows for x86_64 and aarch64)" 48 + ;; 49 + aarch64-linux) 50 + # Linux can cross-compile to everything 51 + TARGETS=( 52 + "aarch64-unknown-linux-gnu" 53 + "x86_64-unknown-linux-gnu" 54 + "x86_64-apple-darwin" 55 + "aarch64-apple-darwin" 56 + "x86_64-pc-windows-gnu" 57 + "aarch64-pc-windows-gnullvm" 58 + ) 59 + echo "Building from aarch64-linux: All targets (Linux, macOS, Windows for x86_64 and aarch64)" 60 + ;; 61 + x86_64-darwin) 62 + # macOS cross-compilation is limited 63 + TARGETS=( 64 + "x86_64-apple-darwin" 65 + ) 66 + echo "Building from x86_64-darwin: x86_64-darwin only" 67 + echo "Note: Cross to aarch64-darwin needs rosetta, cross to Linux/Windows needs more setup" 68 + ;; 69 + aarch64-darwin) 70 + # macOS aarch64 can build both macOS targets via rosetta 71 + TARGETS=( 72 + "aarch64-apple-darwin" 73 + "x86_64-apple-darwin" 74 + ) 75 + echo "Building from aarch64-darwin: macOS targets (aarch64 + x86_64 via rosetta)" 76 + echo "Note: Cross to Linux/Windows needs more setup" 77 + ;; 78 + *) 79 + echo "Error: Unknown system: $CURRENT_SYSTEM" 80 + echo "This script supports: x86_64-linux, aarch64-linux, x86_64-darwin, aarch64-darwin" 81 + exit 1 82 + ;; 83 + esac 84 + 85 + echo "" 86 + echo "Will build for: ${TARGETS[*]}" 87 + echo "" 23 88 24 89 # Output directories 25 90 OUTPUT_DIR="binaries" ··· 27 92 mkdir -p "$OUTPUT_DIR" 28 93 mkdir -p "$RELEASES_DIR" 29 94 30 - # Build binaries in release mode 31 - echo "Building binaries..." 32 - cargo build --release -p jacquard-lexicon --bin jacquard-codegen 33 - cargo build --release -p jacquard-lexicon --bin lex-fetch 95 + # Helper function to package for a target 96 + package_target() { 97 + local TARGET=$1 98 + local PACKAGE_NAME="${TARGET_TO_PACKAGE[$TARGET]}" 34 99 35 - # Binary locations 36 - CODEGEN_BIN="target/release/jacquard-codegen" 37 - LEXFETCH_BIN="target/release/lex-fetch" 100 + echo "" 101 + echo "======================================" 102 + echo "Building for $TARGET" 103 + echo "======================================" 38 104 39 - # Verify binaries exist 40 - if [[ ! -f "$CODEGEN_BIN" ]]; then 41 - echo "Error: jacquard-codegen binary not found at $CODEGEN_BIN" 42 - exit 1 43 - fi 44 - 45 - if [[ ! -f "$LEXFETCH_BIN" ]]; then 46 - echo "Error: lex-fetch binary not found at $LEXFETCH_BIN" 47 - exit 1 48 - fi 49 - 50 - # Helper function to package a binary 51 - package_binary() { 52 - local BIN_NAME=$1 53 - local BIN_PATH=$2 54 - local EXTRA_FILES=("${@:3}") # Additional files beyond README and LICENSE 55 - 56 - echo "Packaging ${BIN_NAME}..." 105 + # Build with nix using cross-compilation package 106 + echo "Running: nix build .#${PACKAGE_NAME}" 107 + if ! nix build ".#${PACKAGE_NAME}" -o "result-${TARGET}"; then 108 + echo "Error: nix build failed for $TARGET" 109 + return 1 110 + fi 57 111 58 112 # Names for versioned and unversioned archives 59 - local VERSIONED_NAME="${BIN_NAME}_${TARGET}_v${VERSION}" 60 - local UNVERSIONED_NAME="${BIN_NAME}_${TARGET}" 113 + local VERSIONED_NAME="jacquard-lexicon_${TARGET}_v${VERSION}" 114 + local UNVERSIONED_NAME="jacquard-lexicon_${TARGET}" 61 115 62 116 # Create staging directory 63 117 local STAGE_DIR="/tmp/${VERSIONED_NAME}" 64 118 rm -rf "$STAGE_DIR" 65 119 mkdir -p "$STAGE_DIR" 66 120 67 - # Copy files 68 - cp "$BIN_PATH" "$STAGE_DIR/" 69 - cp LICENSE "$STAGE_DIR/" 70 - cp README.md "$STAGE_DIR/" 71 - for file in "${EXTRA_FILES[@]}"; do 72 - [[ -n "$file" ]] && cp "$file" "$STAGE_DIR/" 73 - done 121 + # Detect if this is a Windows target 122 + if [[ "$TARGET" == *"windows"* ]]; then 123 + # Windows: just binaries, README, LICENSE, example config 124 + mkdir -p "$STAGE_DIR/bin" 125 + cp "result-${TARGET}"/bin/*.exe "$STAGE_DIR/bin/" 2>/dev/null || true 126 + cp LICENSE "$STAGE_DIR/" 127 + cp README.md "$STAGE_DIR/" 74 128 75 - # Strip binary (reduce size) 76 - strip "$STAGE_DIR/$BIN_NAME" || echo "Warning: strip failed, skipping" 129 + # Copy example config to a more Windows-friendly location 130 + mkdir -p "$STAGE_DIR/examples" 131 + cp crates/jacquard-lexicon/lexicons.kdl.example "$STAGE_DIR/examples/" 2>/dev/null || true 132 + else 133 + # Unix (Linux/macOS): full structure with man pages and completions 134 + cp -r "result-${TARGET}"/* "$STAGE_DIR/" 135 + cp LICENSE "$STAGE_DIR/" 136 + cp README.md "$STAGE_DIR/" 137 + fi 77 138 78 139 # Create versioned archive (for releases) 79 140 cd /tmp 80 - tar -cJf "${VERSIONED_NAME}.tar.xz" "$VERSIONED_NAME" 81 - mv "${VERSIONED_NAME}.tar.xz" "$PROJECT_ROOT/$RELEASES_DIR/" 82 - echo " Created: ${RELEASES_DIR}/${VERSIONED_NAME}.tar.xz" 83 141 84 - # Rename staging directory for unversioned archive 85 - mv "$VERSIONED_NAME" "$UNVERSIONED_NAME" 142 + # Use .zip for Windows, .tar.xz for Unix 143 + if [[ "$TARGET" == *"windows"* ]]; then 144 + zip -r "${VERSIONED_NAME}.zip" "$VERSIONED_NAME" 145 + mv "${VERSIONED_NAME}.zip" "$PROJECT_ROOT/$RELEASES_DIR/" 146 + echo " Created: ${RELEASES_DIR}/${VERSIONED_NAME}.zip" 86 147 87 - # Create unversioned archive (tracked in git) 88 - tar -cJf "${UNVERSIONED_NAME}.tar.xz" "$UNVERSIONED_NAME" 89 - mv "${UNVERSIONED_NAME}.tar.xz" "$PROJECT_ROOT/$OUTPUT_DIR/" 90 - echo " Created: ${OUTPUT_DIR}/${UNVERSIONED_NAME}.tar.xz" 148 + # Rename and create unversioned archive 149 + mv "$VERSIONED_NAME" "$UNVERSIONED_NAME" 150 + zip -r "${UNVERSIONED_NAME}.zip" "$UNVERSIONED_NAME" 151 + mv "${UNVERSIONED_NAME}.zip" "$PROJECT_ROOT/$OUTPUT_DIR/" 152 + echo " Created: ${OUTPUT_DIR}/${UNVERSIONED_NAME}.zip" 153 + else 154 + tar -cJf "${VERSIONED_NAME}.tar.xz" "$VERSIONED_NAME" 155 + mv "${VERSIONED_NAME}.tar.xz" "$PROJECT_ROOT/$RELEASES_DIR/" 156 + echo " Created: ${RELEASES_DIR}/${VERSIONED_NAME}.tar.xz" 157 + 158 + # Rename and create unversioned archive 159 + mv "$VERSIONED_NAME" "$UNVERSIONED_NAME" 160 + tar -cJf "${UNVERSIONED_NAME}.tar.xz" "$UNVERSIONED_NAME" 161 + mv "${UNVERSIONED_NAME}.tar.xz" "$PROJECT_ROOT/$OUTPUT_DIR/" 162 + echo " Created: ${OUTPUT_DIR}/${UNVERSIONED_NAME}.tar.xz" 163 + fi 91 164 92 165 # Cleanup 93 166 rm -rf "$UNVERSIONED_NAME" 167 + rm -f "$PROJECT_ROOT/result-${TARGET}" 94 168 cd "$PROJECT_ROOT" 95 169 } 96 170 97 - # Package jacquard-codegen 98 - package_binary "jacquard-codegen" "$CODEGEN_BIN" 99 - 100 - # Package lex-fetch (with lexicons.kdl) 101 - package_binary "lex-fetch" "$LEXFETCH_BIN" "lexicons.kdl" 171 + # Build for all targets 172 + for target in "${TARGETS[@]}"; do 173 + package_target "$target" || echo "Warning: build failed for $target, continuing..." 174 + done 102 175 103 176 # Print summary 104 177 echo "" 105 178 echo "Packaging complete!" 106 179 echo "" 107 180 echo "Tracked archives (binaries/):" 108 - ls -lh "$OUTPUT_DIR"/*.tar.xz 181 + ls -lh "$OUTPUT_DIR"/*.tar.xz 2>/dev/null || true 182 + ls -lh "$OUTPUT_DIR"/*.zip 2>/dev/null || true 109 183 echo "" 110 184 echo "Release archives (binaries/releases/):" 111 - ls -lh "$RELEASES_DIR"/*.tar.xz 185 + ls -lh "$RELEASES_DIR"/*.tar.xz 2>/dev/null || true 186 + ls -lh "$RELEASES_DIR"/*.zip 2>/dev/null || true 112 187 113 188 # Generate checksums for tracked archives 114 189 echo "" 115 190 echo "Generating checksums for tracked archives..." 116 191 cd "$OUTPUT_DIR" 117 - sha256sum *.tar.xz > SHA256SUMS 192 + sha256sum *.tar.xz *.zip 2>/dev/null > SHA256SUMS || true 118 193 echo "Checksums written to ${OUTPUT_DIR}/SHA256SUMS" 119 194 cat SHA256SUMS 120 195 ··· 122 197 echo "" 123 198 echo "Generating checksums for release archives..." 124 199 cd "$PROJECT_ROOT/$RELEASES_DIR" 125 - sha256sum *.tar.xz > SHA256SUMS 200 + sha256sum *.tar.xz *.zip 2>/dev/null > SHA256SUMS || true 126 201 echo "Checksums written to ${RELEASES_DIR}/SHA256SUMS" 127 202 cat SHA256SUMS