This directory contains runnable example projects using hs-bindgen. These
examples include bindings and toy programs for various C libraries.
- cross-compilation - Demonstrates how to generate bindings for different target architectures (e.g., generating ARM bindings on x86_64). Includes a Nix development environment, QEMU testing, and detailed beginner-friendly documentation.
bundled-cc-minisatc-qrcodec-yamllibpcap
The examples are only tested (in CI) on Ubuntu. They might work on other distributions such as Windows or MacOS as well, but we offer no guarantees.
The following software should be installed before trying to build and run one of the examples:
- A version of GHC that is compatible with
hs-bindgen, such as9.6.x - A version of Cabal that is compatible with
hs-bindgen, such as3.16.x - A version of LLVM (and Clang) that is compatible with
hs-bindgen, such as15
There are a number of other "basic" packages that have to be installed on the
system as well, such as git and a gcc toolchain. Most systems will have
these installed already.
Most example projects still require other software to be installed. These
prerequisites are described in the README file of each example project. With the
requirements met, building and running an example should be as simple as
cd-ing into the example's directory and running the generate-and-run.sh
script.
To run the c-minisat example:
cd c-minisat
./generate-and-run.shNew examples should be put into their own directory under the
REPOSITORY_ROOT/examples directory. The author of the new example is in
principle free to structure the new example project as they wish, but it is
advisable to follow the structure of existing example projects, in part to make
it easy to integrate the new example with our CI.
Let's say we we want to create an example project for the libfoo C library,
and also build and run it in CI. Integration is rather straightforward as long
as the following requirements are met:
-
Create a directory at
REPOSITORY_ROOT/examples/libfoo -
Create a Haskell package at
REPOSITORY_ROOT/examples/libfoo/hs-project/libfoo.cabal-
The package should include an executable component
libfoo-bin -
The package should have its own project file at
REPOSITORY_ROOT/examples/libfoo/hs-project/cabal.project, which should contain at least:import: ../../../cabal.project.base packages: . ../../../hs-bindgen-runtime
If other
hs-bindgenpackages are required, add them topackages.
-
-
Add a script at
REPOSITORY_ROOT/examples/libfoo/generate-and-run.sh -
The script should install the
libfooC package (locally) -
The script should run
hs-bindgen-clionlibfoo's header files and put the generated modules into the Haskell project -
The script should make sure that the Haskell package can find the installed
libfoopackage. For locally installed packages, this probably means settingLD_LIBRARY_PATHand updating theREPOSITORY_ROOT/examples/libfoo/hs-project/cabal.project.localfile so that it includes:package libfoo extra-include-dirs: -- insert absolute path to installation directory for header files here extra-lib-dirs: -- insert absolute path to installation directory for dll files here
If a
cabal.project.localfile already exists, then the file should be updated to include the lines above. Otherwise, it should create the file with the lines above. -
The script should run the Haskell executable
-
Add a composite action by creating a new file at
REPOSITORY_ROOT/.github/actions/examples/libfoo.action.yml- The composite action should install example-specific prerequisites, such as system packages
- The composite action should run the
REPOSITORY_ROOT/examples/libfoo/generate-and-run.shscript
-
Update the workflow file at
REPOSITORY_ROOT/.github/workflows/examples.yml-
In the
setup-matrixjob, add a line forlibfooto the "default" matrix, or add it to both the "default" and "comprehensive" matrix. For example, the inserted line could look like:'{"runner": "ubuntu-latest" , "ghc-version": "9.4", "cabal-version": "3.16", "llvm-version": "15", "example": "libfoo"},'The difference between the "default" and "comprehensive" matrix is that the "comprehensive" matrix only runs on nightly CI. The "default" matrix runs as part of the regular development cycle (PRs, merge queue,
main). If you addlibfooto the default matrix, then you should also add it to the comprehensive matrix. Within each section, entries should be in alphabetical order. -
Towards the end of the file, add a step that calls the
libfoocomposite action (again, in alphabetical order):- name: 🧪 Build and run libfoo example if: ${{ matrix.example == 'libfoo' }} uses: ./.github/actions/examples/libfoo
-
Now create a PR with these changes and (hopefully) you will observe that a new job is run that tests the new example project.
By default each example bundles or downloads its C dependency, builds it
from source, and points hs-bindgen-cli at the resulting headers. This
guarantees a known-good version but means the example doesn't exercise the
"system library" code path that real users hit.
Where the library is also available as a system package on common distros,
expose a single override env var (e.g. LIBFOO_INCLUDE_DIR) that, when
set, skips the bundled build and points hs-bindgen-cli at the system
headers instead.
examples/libpcap/generate.sh:14 is the worked example:
module_flags=(
-I "${PCAP_INCLUDE_DIR:-./libpcap}"
...
)PCAP_INCLUDE_DIR=/usr/include ./generate.sh skips the cmake/make
detour and uses Alpine's apk add libpcap-dev install. The Alpine CI
workflow (.github/workflows/alpine.yml) consumes exactly this entry
point. The example's extra-libraries: pcap directive in the cabal
file already finds the system libpcap.so via standard search paths,
so no further plumbing is needed.