bindings-DSL 1.0.25 → 1.1.0
raw patch · 6 files changed
+366/−74 lines, 6 filesdep ~basenew-uploaderPVP ok
version bump matches the API change (PVP)
Dependency ranges changed: base
API changes (from Hackage documentation)
Files
- Bindings/Utilities.hs +3/−3
- CHANGELOG.md +152/−0
- ChangeLog +0/−35
- README.md +134/−0
- bindings-DSL.cabal +14/−11
- bindings.dsl.h +63/−25
Bindings/Utilities.hs view
@@ -1,6 +1,6 @@-module Bindings.Utilities (- storableCast,- storableCastArray,+module Bindings.Utilities+ ( storableCast+ , storableCastArray ) where import Foreign.C
+ CHANGELOG.md view
@@ -0,0 +1,152 @@+# Changelog++This repository releases eleven packages, each versioned independently under the+PVP and tagged as `<pkg>-<version>` (see [RELEASE.md](RELEASE.md)). Each entry+below names the package and version it shipped in.++## bindings-DSL 1.1.0++- Add `#array2d_field` for members like `char x[8][255]`, which `#array_field`+ miscompiled: it divided the member size by `sizeOf (Ptr CChar)` and produced+ 255 rows instead of 8. Declare the type as the row pointer (`Ptr CChar`);+ `peek` returns pointers into the struct, `poke` memmoves each row back.++- `#array_field` and `#union_array_field` element counts are now baked in from C+ (`sizeof member / sizeof member[0]`) instead of dividing by the declared+ Haskell type's `sizeOf` at runtime. Beware: if your declared type's `sizeOf`+ does not match the C element size, the count changes. The old code never read+ past the member; the new code reads `count * sizeOf` bytes and will overrun it+ if the declared type is too big. Correctly declared bindings generate identical+ code.++- The code generated by `#stoptype` now carries `{-# LINE #-}` pragmas pointing+ back into the `.hsc` file, so GHC reports an error in a struct field at that+ field rather than at an unrelated line before the `#starttype` block.++## bindings-gpgme 0.2.0++- Hide the trust item API when building against gpgme 2.0, which removed it+ (deprecated since 1.14), so the binding compiles against both the 1.x and 2.x+ series.++- Add the ECC, ECDSA, ECDH and EDDSA public key algorithm constants, and+ `gpgme_op_delete_ext`.++- Catch up with the API added between gpgme 1.6 and 2.1, which the binding had+ never covered. Key creation and editing (`createkey`, `adduid`, `keysign`,+ `setexpire`, `interact` and friends), context flags (offline, sender, status+ callback, `set_ctx_flag`), the encrypt, decrypt, export and keylist mode flags,+ importing and exporting by key rather than by pattern, TOFU, and the various+ new struct members. Each is guarded on the gpgme version that introduced it, so+ the binding still builds against older releases.++- Expose the key capability and status bits (revoked, expired, can_encrypt,+ secret, ...) as `c'gpgme_key_*`, `c'gpgme_subkey_*` and related accessors. They+ are C bitfields, which have no `offsetof` and so could not be bound as struct+ fields; they now go through `inlines.c`. Previously there was no way to tell+ whether a listed key was revoked or able to encrypt.++- Bind `gpgme_error_from_syserror` through `inlines.c`. It is a static inline in+ `gpgme.h` rather than a symbol in libgpgme, so a `#ccall` linked but failed to+ resolve at load time.++- Add `gpgme_invalid_key_next`. The `next` member of `_gpgme_invalid_key` is+ bound as an embedded struct instead of a pointer, so `invalid_recipients` and+ `invalid_signers` cannot be walked. Fixing the field would change the type of a+ generated accessor and break its callers, so it is left as it is and this is+ added alongside.++## bindings-hdf5 0.1.3++- Hide `H5D_MPIO_FILTERS` on HDF5 1.10 and later, which removed it.++## bindings-libcddb 0.3.1++- Declare `CDDB_CATEGORY` as `const char*`, matching libcddb. The accessor was+ instantiated with `char*`, so the generated return type dropped the const on+ the pointed-to char. GCC warned about that before 14 and rejects it from 14 on.++## All packages++- Every `bindings-*` package accepts `bindings-DSL < 1.2`, and is released so+ that the new bound reaches Hackage. Releases are now tagged per package+ (`bindings-DSL-1.1.0`, `bindings-hdf5-0.1.3`, ...) rather than with a bare+ version, which was ambiguous once more than one package was being released.++- Replace Travis with GitHub Actions, building `bindings-DSL` and+ `bindings-posix` across GHC 9.6 to 9.12, `bindings-gpgme` against the latest+ gpgme 1.x and 2.x, `bindings-libcddb` under GCC 14, and `bindings-hdf5`.++## bindings-DSL 1.0.25++- No changes to the DSL itself. `bindings-posix` stopped assuming that OS X+ provides everything POSIX asks for.++## bindings-DSL 1.0.24++- Add `#num_pattern` and `#fractional_pattern`, for matching numeric constants in+ patterns.++- Support interruptible calls with `#ccall_interruptible`.++- Silence GHC warnings for `Storable` instances of field-less types, printf+ format warnings, and shadowed temporary variables.++## bindings-DSL 1.0.23++- No changes to the DSL itself. `bindings-posix` gained bindings for message+ queues.++## bindings-DSL 1.0.22++- No changes to the DSL itself. `bindings-gpgme` gained pinentry modes, I/O+ wrappers and the subkey curve field; `bindings-hdf5` gained its low level+ interface.++## bindings-DSL 1.0.21++- No changes to the DSL itself. Maintainership passed to John Wiegley.++## bindings-DSL 1.0.20++- Include `<cinttypes>` instead of `<inttypes.h>` under a C++ compiler, which+ otherwise lacks `PRIuMAX` and `PRIdMAX` and fails to expand the macros. Note+ that this requires building the user library with `-std=c++11`.++## bindings-DSL 1.0.19++- Add a change log.++- Support unsafe calls with experimental `#ccall_unsafe`.++## bindings-DSL 1.0.18++This version has a bug. Marked as such in Hackage.++- Introduce a silly bug to solve a problem that does not exist.++## bindings-DSL 1.0.17++- Add `Bindings.Utilities` module for general utilities.++- Change repository to git.++- Don't underestimate size of field arrays with dimension >= 2.++- Change `#callback` to `#callback_t`.++- Keep source of `bindings-*` libraries with `bindings-DSL`.++- Add examples used in tutorial.++## bindings-DSL 1.0.16++- Use correct value for `Storable` alignment instead of copying `sizeOf`.++## bindings-DSL 1.0.15++- New hsc2hs template doesn't include some headers, so include it ourselves.++## bindings-DSL 1.0.14++- Add functions to get pointers to fields from pointers to structures.
− ChangeLog
@@ -1,35 +0,0 @@-Changes in 1.0.19--* Add a change log.--* Support unsafe calls with experimental #ccall_unsafe.--Changes in 1.0.18 (This version has a bug. Marked as such in Hackage)--* Introduce a silly bug to solve a problem that does not exist.--Changes in 1.0.17--* Add Bindings.Utilities module for general utilities.--* Change repository to git.--* Don't underestimate size of field arrays with dimension >= 2.--* Change #callback to #callback_t.--* Keep source of bindings-* libraries with bindings-DSL.--* Add examples used in tutorial.--Changes in 1.0.16--* Use correct value for Storable alignment instead of copying sizeOf.--Changes in 1.0.15--* New hsc2hs template doesn't include some headers, so include it ourselves.--Changes in 1.0.14--* Add functions to get pointers to fields from pointers to structures.
+ README.md view
@@ -0,0 +1,134 @@+# bindings-dsl++[](https://hackage.haskell.org/package/bindings-DSL)+[](https://github.com/rethab/bindings-dsl/actions/workflows/ci.yml)+++`bindings-DSL` is a domain specific language for writing Haskell FFI bindings, on+top of [hsc2hs](https://downloads.haskell.org/ghc/latest/docs/users_guide/utils.html#writing-haskell-interfaces-to-c-code-hsc2hs).+It is a set of C macros that describe a C interface, and from which hsc2hs+extracts the Haskell code that mimics it: `#ccall` for a function, `#starttype`+and `#field` for a struct, `#num` for a constant, and so on.++```haskell+#include <bindings.dsl.h>+#include <gpgme.h>++module Bindings.Gpgme where+#strict_import++#num GPGME_PK_RSA++#starttype struct _gpgme_key+#field revoked , CUInt+#field fpr , CString+#stoptype++#ccall gpgme_get_key , <gpgme_ctx_t> -> CString -> Ptr <gpgme_key_t> -> CInt -> IO <gpgme_error_t>+```++Haskell names are derived from the C names automatically, so there is no naming+scheme to invent and no chance of the two drifting apart: a constant becomes+`c'GPGME_PK_RSA`, a struct becomes a `Storable` instance with `p'_gpgme_key'fpr`+accessors, and a function becomes `c'gpgme_get_key`. Structs are laid out by the+C compiler rather than by hand, which is what makes the bindings portable across+platforms where sizes and padding differ. There are also macros for binding+inline functions and macro functions, which have no symbol to call.++Full documentation, including a tutorial, is on the+[wiki](https://github.com/rethab/bindings-dsl/wiki). Release notes are in+[CHANGELOG.md](CHANGELOG.md).++## Packages++`bindings-DSL` is the DSL itself. The bindings built on top of it are released as+separate packages from this repository:++| Package | Hackage |+| --- | --- |+| [bindings-directfb](bindings-directfb) | [](https://hackage.haskell.org/package/bindings-directfb) |+| [bindings-fann](bindings-fann) | [](https://hackage.haskell.org/package/bindings-fann) |+| [bindings-glib](bindings-glib) | [](https://hackage.haskell.org/package/bindings-glib) |+| [bindings-gpgme](bindings-gpgme) | [](https://hackage.haskell.org/package/bindings-gpgme) |+| [bindings-gsl](bindings-gsl) | [](https://hackage.haskell.org/package/bindings-gsl) |+| [bindings-hdf5](bindings-hdf5) | [](https://hackage.haskell.org/package/bindings-hdf5) |+| [bindings-libcddb](bindings-libcddb) | [](https://hackage.haskell.org/package/bindings-libcddb) |+| [bindings-libffi](bindings-libffi) | [](https://hackage.haskell.org/package/bindings-libffi) |+| [bindings-posix](bindings-posix) | [](https://hackage.haskell.org/package/bindings-posix) |+| [bindings-sqlite3](bindings-sqlite3) | [](https://hackage.haskell.org/package/bindings-sqlite3) |++## Contributing++Contributions are welcome, whether to the DSL itself or to any of the bindings.+Bugs, missing coverage of a C API, and build problems can be raised in the+[issue tracker](https://github.com/rethab/bindings-dsl/issues); pull requests are+just as welcome. CI builds the DSL and the bindings across several GHC versions+and library releases, so open a pull request and let it run.++## Building bindings-gpgme on macOS with Homebrew++`gpgme.h` includes `<gpg-error.h>`, and Homebrew ships `libgpg-error` as a keg of+its own, so both prefixes have to be on the include and library paths. Passing+only gpgme's prefix leaves the nested include unresolved and the build fails with+a misleading complaint about a missing `gpgme.h`.++```sh+brew install gpgme libgpg-error++cabal build bindings-gpgme \+ --extra-include-dirs="$(brew --prefix gpgme)/include" \+ --extra-include-dirs="$(brew --prefix libgpg-error)/include" \+ --extra-lib-dirs="$(brew --prefix gpgme)/lib" \+ --extra-lib-dirs="$(brew --prefix libgpg-error)/lib"+```++With stack, list both prefixes in `stack.yaml` (`brew --prefix` is+`/opt/homebrew` on Apple silicon and `/usr/local` on Intel):++```yaml+extra-include-dirs:+ - /opt/homebrew/opt/gpgme/include+ - /opt/homebrew/opt/libgpg-error/include+extra-lib-dirs:+ - /opt/homebrew/opt/gpgme/lib+ - /opt/homebrew/opt/libgpg-error/lib+```++## Cross compilation++bindings-DSL does not work with `hsc2hs --cross-compile` (see+[#38](https://github.com/rethab/bindings-dsl/issues/38)). Every construct+(`#strict_import`, `#starttype`, `#ccall`, ...) is a custom hsc2hs construct that+emits Haskell while the generated C program runs. Cross mode never runs that+program: it recognises only a fixed set of built-in directives and rejects+everything else with++```+directive strict_import cannot be handled in cross-compilation mode+```++`--via-asm` does not help, it only changes how cross mode extracts constants. No+change to `bindings.dsl.h` can lift this; it needs hsc2hs to support custom+constructs when cross compiling.++Cross compiling still works if you can execute target binaries, e.g. with+qemu-user plus `binfmt_misc`, or wine for Windows targets. Cabal gets in the way+here: it passes `-x` to hsc2hs whenever the host platform differs from the build+platform, and hsc2hs has no flag to undo that. Point Cabal at a wrapper that+drops the flag and lets the normal compile-and-run path proceed under emulation:++```sh+#!/bin/sh+# hsc2hs-no-cross, used via --with-hsc2hs=/path/to/hsc2hs-no-cross+for a in "$@"; do+ shift+ case "$a" in+ -x|--cross-compile) ;;+ *) set -- "$@" "$a" ;;+ esac+done+exec hsc2hs "$@"+```++Otherwise, write plain `.hsc` without bindings-DSL for the modules you need to+cross compile.
bindings-DSL.cabal view
@@ -1,6 +1,6 @@-cabal-version: >= 1.8+cabal-version: >= 1.10 name: bindings-DSL-homepage: https://github.com/jwiegley/bindings-dsl/wiki+homepage: https://github.com/rethab/bindings-dsl/wiki synopsis: FFI domain specific language, on top of hsc2hs. description: @@ -13,29 +13,32 @@ to help write bindings to inline functions or macro functions. Documentation is available at package homepage: .- <https://github.com/jwiegley/bindings-dsl/wiki>+ <https://github.com/rethab/bindings-dsl/wiki> . The extra module Bindings.Utilities will contain tools that may be convenient when working with FFI.-version: 1.0.25+version: 1.1.0 license: BSD3 license-file: LICENSE-maintainer: John Wiegley <johnw@newartisans.com>+maintainer: Reto <rethab@protonmail.ch> author: Maurício C. Antunes stability: Stable API, well tested, portable, used in commercial code. build-type: Simple-bug-reports: https://github.com/jwiegley/bindings-dsl/issues+bug-reports: https://github.com/rethab/bindings-dsl/issues category: FFI-extra-source-files: ChangeLog+extra-source-files: README.md , CHANGELOG.md library+ default-language:+ Haskell2010+ include-dirs: . install-includes: bindings.dsl.h , bindings.cmacros.h- build-depends: base >= 0 && < 1000+ build-depends: base >= 4 && < 5 exposed-modules: Bindings.Utilities source-repository head type: git- location: git://github.com/jwiegley/bindings-dsl+ location: https://github.com/rethab/bindings-dsl branch: master source-repository this type: git- location: git://github.com/jwiegley/bindings-dsl- tag: 1.0.25+ location: https://github.com/rethab/bindings-dsl+ tag: bindings-DSL-1.1.0
bindings.dsl.h view
@@ -18,6 +18,25 @@ #include <inttypes.h> #endif +static char bc_srcfile[1000] = "";++/* hsc2hs calls hsc_line before every chunk of Haskell output. We keep the+ * original behaviour and additionally remember the file name, so that the+ * code generated by hsc_stoptype can be attributed to the .hsc source. */+#undef hsc_line+#define hsc_line(line,file) \+ { \+ strncpy(bc_srcfile,file,sizeof bc_srcfile - 1); \+ bc_srcfile[sizeof bc_srcfile - 1] = '\0'; \+ printf("{-# LINE %d \"%s\" #-}\n",line,file); \+ } \++#define bc_lineprag(l) \+ { \+ if (bc_srcfile[0]) \+ printf("{-# LINE %d \"%s\" #-}\n",(int)(l),bc_srcfile); \+ } \+ #define hsc_strict_import(dummy) printf( \ "import Foreign.Ptr (Ptr,FunPtr,plusPtr)\n" \ "import Foreign.Ptr (wordPtrToPtr,castPtrToFunPtr)\n" \@@ -26,6 +45,7 @@ "import Foreign.C.String (CString,CStringLen,CWString,CWStringLen)\n" \ "import Foreign.Marshal.Alloc (alloca)\n" \ "import Foreign.Marshal.Array (peekArray,pokeArray)\n" \+ "import Foreign.Marshal.Utils (moveBytes)\n" \ "import Data.Int\n" \ "import Data.Word\n" \ ); \@@ -222,8 +242,8 @@ printf(" -> (");bc_typemarkup(# type);printf(")\n"); \ static struct {- int n, is_union[500], is_fam[500];- uintmax_t array_size[500], offset[500];+ int n, is_union[500], is_fam[500], is_2d[500], line[500];+ uintmax_t array_size[500], elem_size[500], offset[500]; char fname[500][1000], ftype[500][1000]; } bc_fielddata; @@ -240,6 +260,7 @@ ptrdiff_t typealign = (char*)&bc_refdata.v - (char*)&bc_refdata; \ bc_fielddata.n = 0; \ char typename[] = # name; \+ int typeline = __LINE__; \ int index; \ int standalone_deriving = 0; \ @@ -248,8 +269,11 @@ bc_fielddata.offset[index] = (uintmax_t) \ ((char*)&bc_refdata.v.name - (char*)&bc_refdata.v); \ bc_fielddata.array_size[index] = 0; \+ bc_fielddata.elem_size[index] = 0; \ bc_fielddata.is_union[index] = u; \ bc_fielddata.is_fam[index] = f; \+ bc_fielddata.is_2d[index] = 0; \+ bc_fielddata.line[index] = __LINE__; \ strcpy(bc_fielddata.fname[index],# name); \ strcpy(bc_fielddata.ftype[index],type); \ @@ -264,13 +288,22 @@ #define hsc_array_field(name,type) \ bc_basicfield(name,# type,0,0); \- bc_fielddata.array_size[index] = sizeof bc_refdata.v.name \+ bc_fielddata.array_size[index] = sizeof bc_refdata.v.name; \+ bc_fielddata.elem_size[index] = sizeof bc_refdata.v.name[0] \ #define hsc_union_array_field(name,type) \ bc_basicfield(name,# type,1,0); \- bc_fielddata.array_size[index] = sizeof bc_refdata.v.name \+ bc_fielddata.array_size[index] = sizeof bc_refdata.v.name; \+ bc_fielddata.elem_size[index] = sizeof bc_refdata.v.name[0] \ +#define hsc_array2d_field(name,type) \+ bc_basicfield(name,# type,0,0); \+ bc_fielddata.array_size[index] = sizeof bc_refdata.v.name; \+ bc_fielddata.elem_size[index] = sizeof bc_refdata.v.name[0]; \+ bc_fielddata.is_2d[index] = 1 \+ #define hsc_stoptype(dummy) \+ bc_lineprag(typeline); \ printf("data ");bc_conid(typename);printf(" = "); \ bc_conid(typename);printf("{\n"); \ int i; \@@ -297,14 +330,17 @@ } \ for (i=0; i < bc_fielddata.n; i++) \ { \+ bc_lineprag(bc_fielddata.line[i]); \ bc_fieldoffset(typename,bc_fielddata.fname[i]); \ printf(" p = plusPtr p %" PRIuMAX "\n",bc_fielddata.offset[i]); \+ bc_lineprag(bc_fielddata.line[i]); \ bc_fieldoffset(typename,bc_fielddata.fname[i]); \ printf(" :: Ptr (");bc_conid(typename);printf(") -> "); \ printf("Ptr (");bc_typemarkup(bc_fielddata.ftype[i]);printf(")\n"); \ } \ for (i=0; i < bc_fielddata.n; i++) if (bc_fielddata.is_union[i]) \ { \+ bc_lineprag(bc_fielddata.line[i]); \ bc_unionupdate(typename,bc_fielddata.fname[i]); \ printf(" :: ");bc_conid(typename);printf(" -> "); \ if (bc_fielddata.array_size[i] > 0) printf("["); \@@ -316,13 +352,9 @@ printf(" v vf = alloca $ \\p -> do\n"); \ printf(" poke p v\n"); \ if (bc_fielddata.array_size[i] > 0) \- { \- printf(" let s%d = div %" PRIuMAX " $ sizeOf $ (undefined :: ", \- i, bc_fielddata.array_size[i]); \- bc_typemarkup(bc_fielddata.ftype[i]); \- printf(")\n pokeArray (plusPtr p %" PRIuMAX ") $ take s%d vf", \- bc_fielddata.offset[i], i); \- } \+ printf(" pokeArray (plusPtr p %" PRIuMAX ") $ take %" PRIuMAX " vf", \+ bc_fielddata.offset[i], \+ bc_fielddata.array_size[i] / bc_fielddata.elem_size[i]); \ else \ printf(" pokeByteOff p %" PRIuMAX " vf", \ bc_fielddata.offset[i]); \@@ -337,6 +369,7 @@ printf(" vu}\n"); \ } \ } \+ bc_lineprag(typeline); \ printf("instance Storable "); \ bc_conid(typename);printf(" where\n"); \ printf(" sizeOf _ = %" PRIuMAX "\n alignment _ = %" PRIuMAX "\n", \@@ -347,14 +380,15 @@ printf(" v%d <- ",i); \ if (bc_fielddata.is_fam[i]) \ printf("return []"); \+ else if (bc_fielddata.is_2d[i]) \+ printf("return [plusPtr _p (%" PRIuMAX " + %" PRIuMAX " * k) " \+ "| k <- [0 .. %" PRIuMAX "]]", \+ bc_fielddata.offset[i], bc_fielddata.elem_size[i], \+ bc_fielddata.array_size[i] / bc_fielddata.elem_size[i] - 1); \ else if (bc_fielddata.array_size[i] > 0) \- { \- printf ("let s%d = div %" PRIuMAX " $ sizeOf $ (undefined :: ", \- i, bc_fielddata.array_size[i]); \- bc_typemarkup(bc_fielddata.ftype[i]); \- printf(") in peekArray s%d (plusPtr _p %" PRIuMAX ")", \- i, bc_fielddata.offset[i]); \- } \+ printf("peekArray %" PRIuMAX " (plusPtr _p %" PRIuMAX ")", \+ bc_fielddata.array_size[i] / bc_fielddata.elem_size[i], \+ bc_fielddata.offset[i]); \ else \ printf("peekByteOff _p %" PRIuMAX "", bc_fielddata.offset[i]); \ printf("\n"); \@@ -370,14 +404,18 @@ if (bc_fielddata.is_fam[i]) \ printf(" pokeArray (plusPtr _p %" PRIuMAX ") v%d", \ bc_fielddata.offset[i],i); \+ else if (bc_fielddata.is_2d[i]) \+ printf(" sequence_ [moveBytes (plusPtr _p (%" PRIuMAX \+ " + %" PRIuMAX " * k)) r %" PRIuMAX \+ " | (k,r) <- zip [0 .. %" PRIuMAX "] v%d]", \+ bc_fielddata.offset[i], bc_fielddata.elem_size[i], \+ bc_fielddata.elem_size[i], \+ bc_fielddata.array_size[i] / bc_fielddata.elem_size[i] - 1, i); \ else if (bc_fielddata.array_size[i] > 0) \- { \- printf(" let s%d = div %" PRIuMAX " $ sizeOf $ (undefined :: ", \- i, bc_fielddata.array_size[i]); \- bc_typemarkup(bc_fielddata.ftype[i]); \- printf(")\n pokeArray (plusPtr _p %" PRIuMAX ") (take s%d v%d)", \- bc_fielddata.offset[i], i, i); \- } \+ printf(" pokeArray (plusPtr _p %" PRIuMAX ") (take %" PRIuMAX \+ " v%d)", \+ bc_fielddata.offset[i], \+ bc_fielddata.array_size[i] / bc_fielddata.elem_size[i], i); \ else \ printf(" pokeByteOff _p %" PRIuMAX " v%d", \ bc_fielddata.offset[i],i); \