Avoid absolute path hack

This commit is contained in:
George Thomas 2026-02-19 15:00:46 +00:00
parent 25875c7dc1
commit ddc8b9097d
3 changed files with 34 additions and 22 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
dist* dist*
result result
target target
cabal.project.local*

View File

@ -1,13 +1,5 @@
packages: . 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 -- 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 -- 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 -- so we specify the equivalent commit SHAs manually instead

View File

@ -5,7 +5,12 @@ set -euo pipefail
# Generate Haskell FFI bindings from Rust source code. # 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). # 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" echo " Raw header written to $HEADER"
# --- Step 3: Patch the header for hs-bindgen compatibility --- # --- 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 ===" 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 ' awk '
# State machine for enum->typedef+define transformation # State machine for enum->typedef+define transformation
/^enum [A-Za-z_][A-Za-z0-9_]* \{$/ { /^enum [A-Za-z_][A-Za-z0-9_]* \{$/ {
@ -94,11 +102,11 @@ pending_enum && /^typedef [A-Za-z0-9_]+ / {
echo " Patched header at $HEADER" echo " Patched header at $HEADER"
# --- Step 4: Derive system include paths for hs-bindgen's libclang --- # --- 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 # hs-bindgen uses libclang directly, which doesn't know about NixOS's
# non-standard include locations. We extract them from cpp -v and pass # non-standard include locations. We extract them from cpp -v and pass
# all of them — extra paths are harmless. # all of them — extra paths are harmless.
echo "=== Detecting system include paths ==="
CLANG_OPTIONS=() CLANG_OPTIONS=()
while IFS= read -r dir; do while IFS= read -r dir; do
CLANG_OPTIONS+=("--clang-option" "-isystem$dir") CLANG_OPTIONS+=("--clang-option" "-isystem$dir")
@ -122,6 +130,17 @@ hs-bindgen-cli preprocess \
"${CLANG_OPTIONS[@]}" \ "${CLANG_OPTIONS[@]}" \
-I "$RUST_DIR" "$HEADER_NAME" -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 "=== Done ==="
echo "Generated Haskell bindings in $HASKELL_DIR/generated/" echo "Generated Haskell bindings in $HASKELL_DIR/generated/"
echo "Run 'cabal run' to test." echo "Run 'cabal run' to test."