Skip to content

Latest commit

 

History

History
169 lines (132 loc) · 6.4 KB

File metadata and controls

169 lines (132 loc) · 6.4 KB

Examples

This directory contains runnable example projects using hs-bindgen. These examples include bindings and toy programs for various C libraries.

Special examples

  • 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.

Library binding examples

  • bundled-c
  • c-minisat
  • c-qrcode
  • c-yaml
  • libpcap

Running examples

Requirements

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 as 9.6.x
  • A version of Cabal that is compatible with hs-bindgen, such as 3.16.x
  • A version of LLVM (and Clang) that is compatible with hs-bindgen, such as 15

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.

Building and running

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.sh

Adding new examples

New 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.

CI integration

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-bindgen packages are required, add them to packages.

  • Add a script at REPOSITORY_ROOT/examples/libfoo/generate-and-run.sh

  • The script should install the libfoo C package (locally)

  • The script should run hs-bindgen-cli on libfoo'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 libfoo package. For locally installed packages, this probably means setting LD_LIBRARY_PATH and updating the REPOSITORY_ROOT/examples/libfoo/hs-project/cabal.project.local file 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.local file 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.sh script
  • Update the workflow file at REPOSITORY_ROOT/.github/workflows/examples.yml

    • In the setup-matrix job, add a line for libfoo to 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 add libfoo to 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 libfoo composite 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.

System-library mode (optional)

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.