Compare commits

..

12 Commits

Author SHA1 Message Date
George Thomas
352bf8c286 basic enum example 2026-03-30 13:07:41 +01:00
George Thomas
091c70815c wip file dependency reload stuff 2026-03-30 13:07:41 +01:00
George Thomas
111b65c708 format 2026-03-30 13:07:16 +01:00
George Thomas
a06f3076c7 clippy fixes 2026-03-30 13:07:16 +01:00
George Thomas
7d9982112a maybe/optional example 2026-03-30 13:07:16 +01:00
George Thomas
31ae16784a minor refactor for consistency (maybe we should go the other way given sumSlice...) 2026-03-30 13:07:16 +01:00
George Thomas
b788d6685d vector/slice example 2026-03-30 13:07:16 +01:00
George Thomas
63efd03382 pass by (immutable) reference for all non-primitive types 2026-03-30 13:07:16 +01:00
George Thomas
bf0fc90272 dedup base dep 2026-03-30 13:07:16 +01:00
George Thomas
6028db040d Simplify build script
Seeing as we now no longer need to modify generated header files.
2026-03-30 13:07:16 +01:00
George Thomas
7ca469a62d use upstream fix to avoid header patching hack...
bump - no tag for libclang and alpha1 leads to build errors

drop dep

argh, unrelated breaking change, and bad names

exts
2026-03-30 13:07:16 +01:00
George Thomas
b5fd53b5ec stuff from Yuri (maybe squash?)...
things I've changed from the PR:
- dropped legacy (i.e. non-flake) compat stuff, which turns out to account for most of the diff
- dropped `packages.garnet` since it doesn't work with `nix build .#garnet`
- back to using Rust-extended packages everywhere, which _seems_ fine
the rest is just re-inlining things and other refactors

note that changes we keep are essentially:
- not using Crane's extended packages everywhere
- bumping `nix-haskell` to avoid shell hook workaround
- various changes in how we call `nix-haskell`
- using `libCgarnet_rs` name, which Cabal expects
- adding proper non-dev-shell targets, so that e.g. `nix run` works

stuff we should still take a look at:
- seems a bit weird that `garnet-rs` arg to `project.nix` was always same (and therefore now we always pass the ) - might be a mistake, and we're supposed to be using local for local build?
- similar `libCgarnet_rs.a` logic appears in three places - see if we can deduplicate to some extent somehow
- I haven't even checked all steps are necessary
2026-03-30 12:08:05 +01:00
8 changed files with 76 additions and 18 deletions

View File

@ -9,6 +9,8 @@ main = do
hello "Haskell"
helloStruct T{a = True, b = 42}
helloStruct T{a = False, b = maxBound}
helloEnum E1
helloEnum E3
helloShape $ Circle 3.14
helloShape $ Rectangle 10.0 5.0
putStrLn $ "3 + 4 = " <> show (add 3 4)

13
flake.lock generated
View File

@ -527,15 +527,16 @@
"hs-bindgen-src": {
"flake": false,
"locked": {
"lastModified": 1774379170,
"narHash": "sha256-lZV6IdCBf8uCt21qB5mfgLaP9CNgho/HsqjvkulDR2Q=",
"lastModified": 1774599157,
"narHash": "sha256-jgV67xhWzxMwyiyy5RPtu+VQvGTt+FoMXCWJcZ7lczY=",
"owner": "well-typed",
"repo": "hs-bindgen",
"rev": "4b6febb5cc6196835bd2890a3ab27a88dab1806b",
"rev": "3c4af10590d0d09e825a9735e9a03d7f60914e21",
"type": "github"
},
"original": {
"owner": "well-typed",
"ref": "release-0.1-alpha2",
"repo": "hs-bindgen",
"type": "github"
}
@ -560,11 +561,11 @@
"libclang-src": {
"flake": false,
"locked": {
"lastModified": 1774018529,
"narHash": "sha256-Bo5wvityXKCmlnrobtI9WkA1maR7sfkDXo1VOZvrPLk=",
"lastModified": 1774600891,
"narHash": "sha256-LTAyNMY4Vu0vPeEq2wXB0KWY4kGtqtHTRmADjLdkv78=",
"owner": "well-typed",
"repo": "libclang",
"rev": "83387d72a8dfae9f75d27db6b32ea37afab06268",
"rev": "1054474fae403bfb52c7919680cac03d3d3d4237",
"type": "github"
},
"original": {

View File

@ -3,7 +3,7 @@
nix-haskell.url = "github:reflex-frp/nix-haskell";
nixpkgs.follows = "nix-haskell/nixpkgs";
hls-src = { url = "github:haskell/haskell-language-server/2.13.0.0"; flake = false; };
hs-bindgen-src = { url = "github:well-typed/hs-bindgen"; flake = false; };
hs-bindgen-src = { url = "github:well-typed/hs-bindgen/release-0.1-alpha2"; flake = false; };
libclang-src = { url = "github:well-typed/libclang"; flake = false; };
flake-utils.url = "github:numtide/flake-utils";
crane.url = "github:ipetkov/crane";

View File

@ -6,6 +6,13 @@ author: Patrick Aldis
maintainer:
george.thomas@obsidian.systems
patrick.aldis@obsidian.systems
-- aha, nice, this does fix recompilation checking
extra-source-files:
rust/target/debug/garnet_rs.h
-- that could be problematic given file is gitignored? unfortunately this doesn't work
-- tbf, I haven't even looked up the docs, just saw the autocompletion
-- extra-tmp-files:
-- rust/target/debug/garnet_rs.h
common common
default-language: GHC2024
@ -43,11 +50,13 @@ library
GarnetRs.Wrapped
hs-source-dirs: lib
include-dirs: rust/target/debug
-- HLS gives up entirely when the header is malformed if we do this
-- and anyway, I don't think it gives us dependency tracking like `extra-source-files` does
-- includes: garnet_rs.h
extra-bundled-libraries: Cgarnet_rs
build-depends:
hs-bindgen,
hs-bindgen-runtime,
primitive,
template-haskell,
executable garnet

View File

@ -1,6 +1,5 @@
{-# LANGUAGE CApiFFI #-}
{-# LANGUAGE DerivingVia #-}
{-# LANGUAGE FieldSelectors #-}
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE TemplateHaskell #-}
@ -30,7 +29,15 @@ import HsBindgen.TH
import Language.Haskell.TH
import System.Process
import Control.Monad.IO.Class (MonadIO (liftIO))
import Language.Haskell.TH.Syntax (addDependentFile)
import System.Directory.Extra (getCurrentDirectory)
do
-- not sure this does anything - hs-bindgen should already be doing the tracking, and the issues are from elsewhere
-- dir <- liftIO getCurrentDirectory
-- liftIO $ print dir
-- addDependentFile $ dir <> "/rust/target/debug/garnet_rs.h"
systemDirs <- -- TODO bit of a hack
map (Dir . T.unpack . T.strip)
. concatMap (takeWhile (maybe False ((== ' ') . fst) . T.uncons) . dropWhile T.null . T.lines)

View File

@ -1,11 +1,20 @@
{-# LANGUAGE PatternSynonyms #-}
-- TODO automate this sort of high level wrapper boilerplate
-- or look at upstream plans: https://github.com/well-typed/hs-bindgen/issues?q=state%3Aopen%20label%3A%22highlevel%22
module GarnetRs.Wrapped (
T (..),
Raw.E (..),
-- TODO hmm, we don't really want to have to list all of these...
-- is there an option to make them not be patterns at all?
pattern Raw.E1,
pattern Raw.E2,
pattern Raw.E3,
Shape (..),
BTree (..),
hello,
helloStruct,
helloEnum,
helloShape,
add,
sumTree,
@ -38,7 +47,6 @@ data Shape
convertShape :: Shape -> Raw.Shape
convertShape = \case
Circle r -> Raw.Shape Raw.Circle $ Raw.set_shape_circle_circle $ Raw.Circle_Body r
-- hmm, unintuitive name
Rectangle w h -> Raw.Shape Raw.Rectangle $ Raw.set_shape_circle_rectangle $ Raw.Rectangle_Body w h
data BTree a
@ -63,6 +71,9 @@ hello = flip useAsCString $ Raw.hello . unsafeFromPtr
helloStruct :: T -> IO ()
helloStruct = flip with (Raw.hello_struct . unsafeFromPtr) . convertT
helloEnum :: Raw.E -> IO ()
helloEnum = flip with (Raw.hello_enum . unsafeFromPtr)
helloShape :: Shape -> IO ()
helloShape = flip with (Raw.hello_shape . unsafeFromPtr) . convertShape

View File

@ -2,6 +2,22 @@ use std::env;
use std::path::PathBuf;
fn main() {
// this doesn't make _much_ difference really, since this is our only Rust source file
// but it seems it's probably better than not having it
// what we really want is to tell Rust to only regenerate the header file if the Rust code actually compiles
// but we don't have that flexibility
// and it's an issue because cbindgen tries to be fault-tolerant in some ways that don't even seem to make sense
//
// e.g. mis-spell "Option" as "Option" and you get
// void print_optional(Optio<const int8_t*> x);
// instead of
// void print_optional(const int8_t *x);
// and that's only an issue because in HLS TH dependent-file watching gives up after an error
// i.e. once the containing splice has thrown an exception once, the containing file needs a manual edit to kick it
// P.S. strings to stdout?! what a terrible API
// don't get me started in the discoverability of actually then using the terminal for debugging:
// println!("cargo::warning={:?}", env::var("OUT_DIR"));
println!("cargo::rerun-if-changed=lib.rs");
let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
let profile = env::var("PROFILE").unwrap();
cbindgen::Builder::new()

View File

@ -11,7 +11,7 @@ fn say_hello(name: &str) {
}
#[unsafe(no_mangle)]
extern "C" fn hello(c: *const c_char) -> () {
extern "C" fn hello(c: *const c_char) {
say_hello(unsafe { CStr::from_ptr(c) }.to_str().unwrap())
}
@ -23,10 +23,23 @@ struct T {
}
#[unsafe(no_mangle)]
extern "C" fn hello_struct(t: &T) -> () {
extern "C" fn hello_struct(t: &T) {
say_hello(&format!("{:?}", t))
}
#[repr(C)]
#[derive(Debug)]
enum E {
E1,
E2,
E3,
}
#[unsafe(no_mangle)]
extern "C" fn hello_enum(e: &E) {
say_hello(&format!("{:?}", e))
}
#[repr(C)]
#[derive(Debug)]
enum Shape {
@ -35,7 +48,7 @@ enum Shape {
}
#[unsafe(no_mangle)]
extern "C" fn hello_shape(s: &Shape) -> () {
extern "C" fn hello_shape(s: &Shape) {
say_hello(&format!("{:?}", s))
}
@ -80,7 +93,7 @@ enum BTreeC {
#[unsafe(no_mangle)]
extern "C" fn sum_tree(t: &BTreeC) -> i64 {
(unsafe { std::mem::transmute::<_, &BTree<i64>>(t) }).sum()
(unsafe { std::mem::transmute::<&BTreeC, &BTree<i64>>(t) }).sum()
}
#[unsafe(no_mangle)]
@ -89,9 +102,8 @@ extern "C" fn sum_slice(v: *const i64, s: usize) -> i64 {
}
#[unsafe(no_mangle)]
extern "C" fn print_optional(x: Option<&i8>) -> () {
match x {
Some(x) => println!("{}", x / 2),
None => {}
extern "C" fn print_optional(x: Option<&i8>) {
if let Some(x) = x {
println!("{}", x / 2)
}
}