diff --git a/.gitignore b/.gitignore index b75c3e9..d57f756 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ dist* result target +cabal.project.local* diff --git a/cabal.project b/cabal.project index 18a28d6..f3fc4a5 100644 --- a/cabal.project +++ b/cabal.project @@ -1,13 +1,5 @@ packages: . --- TODO a total hack -package garnet - extra-lib-dirs: - /home/gthomas/code/garnet/rust/target/release - /home/gthomas/code/garnet/rust/target/debug - extra-include-dirs: - /home/gthomas/code/garnet/rust - -- https://well-typed.com/blog/2026/02/hs-bindgen-alpha -- Haskell.nix doesn't seem to like `tag: release-0.1-alpha`, which the blog post suggests -- so we specify the equivalent commit SHAs manually instead diff --git a/generate-bindings b/generate-bindings index cfc42bd..462a5ab 100755 --- a/generate-bindings +++ b/generate-bindings @@ -5,7 +5,12 @@ set -euo pipefail # Generate Haskell FFI bindings from Rust source code. # -# Pipeline: cargo build -> cbindgen -> patch header -> hs-bindgen +# Pipeline: +# 1. cargo build - build the Rust static library +# 2. cbindgen - generate a C header from the Rust source +# 3. awk - patch the header for hs-bindgen compatibility +# 4. hs-bindgen-cli - generate Haskell FFI modules from the C header +# 5. cabal configure - point Cabal at the Rust build artifacts # # Prerequisites: run inside the Nix dev shell (provides gcc, cabal, cbindgen, hs-bindgen-cli). @@ -30,18 +35,21 @@ cbindgen \ echo " Raw header written to $HEADER" # --- Step 3: Patch the header for hs-bindgen compatibility --- +# +# Two patches are needed, both due to hs-bindgen limitations with cbindgen's +# output for #[repr(C, u8)] tagged enums: +# +# 1. cbindgen emits: enum Shape_Tag { Circle, Rectangle, }; +# typedef uint8_t Shape_Tag; +# hs-bindgen needs: typedef uint8_t Shape_Tag; +# #define Circle 0 +# #define Rectangle 1 +# See: no upstream issue yet for hs-bindgen +# +# 2. cbindgen emits: union { ... }; (anonymous) +# hs-bindgen needs: union { ... } body; (named) +# See: https://github.com/well-typed/hs-bindgen/issues/1649 echo "=== Patching header ===" - -# Two patches are needed: -# -# Patch 1: Replace cbindgen's enum pattern with typedef + #defines. -# cbindgen emits: enum Shape_Tag { Circle, Rectangle, }; typedef uint8_t Shape_Tag; -# hs-bindgen needs: typedef uint8_t Shape_Tag; #define Circle 0 ... -# -# Patch 2: Name anonymous unions inside structs. -# cbindgen emits: union { ... }; -# hs-bindgen needs: union { ... } body; - awk ' # State machine for enum->typedef+define transformation /^enum [A-Za-z_][A-Za-z0-9_]* \{$/ { @@ -94,11 +102,11 @@ pending_enum && /^typedef [A-Za-z0-9_]+ / { echo " Patched header at $HEADER" # --- Step 4: Derive system include paths for hs-bindgen's libclang --- -echo "=== Detecting system include paths ===" - +# # hs-bindgen uses libclang directly, which doesn't know about NixOS's # non-standard include locations. We extract them from cpp -v and pass # all of them — extra paths are harmless. +echo "=== Detecting system include paths ===" CLANG_OPTIONS=() while IFS= read -r dir; do CLANG_OPTIONS+=("--clang-option" "-isystem$dir") @@ -122,6 +130,17 @@ hs-bindgen-cli preprocess \ "${CLANG_OPTIONS[@]}" \ -I "$RUST_DIR" "$HEADER_NAME" +# --- Step 6: Configure Cabal --- +# +# Point Cabal at the Rust static library and C header. This writes +# cabal.project.local (gitignored) with absolute paths derived from +# the current working directory, avoiding hardcoded paths in cabal.project. +echo "=== Configuring Cabal ===" +cabal configure \ + --extra-lib-dirs="$RUST_DIR/target/debug" \ + --extra-lib-dirs="$RUST_DIR/target/release" \ + --extra-include-dirs="$RUST_DIR" + echo "=== Done ===" echo "Generated Haskell bindings in $HASKELL_DIR/generated/" echo "Run 'cabal run' to test."