Skip to content

Commit df03622

Browse files
committed
Introduces c-rogueutil bindings example
1 parent 4a22762 commit df03622

File tree

11 files changed

+326
-0
lines changed

11 files changed

+326
-0
lines changed

.github/actions/base/action.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,10 @@ runs:
133133
if: runner.os == 'Windows'
134134
uses: ./.github/actions/build-manual-windows
135135

136+
- name: Build c-rogueutil example (Ubuntu)
137+
if: runner.os == 'Linux'
138+
uses: ./.github/actions/examples/c-rogueutil
139+
136140
# Save the cache after building the project AND the manual.
137141
- name: Save cache
138142
uses: actions/cache/save@v4
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
name: c-rogueutil Example
2+
description: Build and run the c-rogueutil example
3+
4+
runs:
5+
using: composite
6+
steps:
7+
- name: Initialize git submodules
8+
shell: bash
9+
run: git submodule update --init --recursive examples/c-rogueutil/rogueutil
10+
11+
- name: Run generation and build script
12+
shell: bash
13+
working-directory: ./examples/c-rogueutil
14+
run: ./generate-and-run.sh

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,6 @@
44
[submodule "alternatives/c2hs"]
55
path = alternatives/tools/c2hs
66
url = https://github.com/haskell/c2hs.git
7+
[submodule "examples/c-rogueutil/rogueutil"]
8+
path = examples/c-rogueutil/rogueutil
9+
url = https://github.com/sakhmatd/rogueutil.git

examples/c-rogueutil/README.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# RogueUtil Haskell Bindings
2+
3+
This example demonstrates generating Haskell bindings for the
4+
[rogueutil](https://github.com/sakhmatd/rogueutil) library, a cross-platform C
5+
library for terminal manipulation.
6+
7+
## Quirks and Workarounds
8+
9+
### 1. Disabling Shared Libraries (`shared: False`)
10+
11+
The `generate-and-run.sh` script sets `shared: False` in `cabal.project.local` to prevent duplicate symbol errors during compilation.
12+
13+
**Why this is needed:**
14+
15+
RogueUtil is an unusual C library that defines function implementations
16+
directly in the header file (`rogueutil.h`) rather than in separate `.c`
17+
files. This is unorthodox C practice, but allows rogueutil to be a header-only
18+
library.
19+
20+
When `hs-bindgen` generates bindings for such a library, it creates C wrapper
21+
functions that get embedded into multiple generated modules (Safe, Unsafe,
22+
FunPtr, Global). During dynamic library compilation, each module includes its
23+
own copy of these wrappers, causing "multiple definition" linker errors like:
24+
25+
```
26+
ld: error: multiple definition of 'printXY'
27+
```
28+
29+
Setting `shared: False` disables dynamic library building, preventing these
30+
conflicts. Static linking only includes each symbol once, avoiding the
31+
duplication problem.
32+
33+
### 2. Enabling Modern C Standard (`-D_POSIX_C_SOURCE=200809L`)
34+
35+
The binding generation command includes the clang option
36+
`-D_POSIX_C_SOURCE=200809L`:
37+
38+
```bash
39+
--clang-option=-D_POSIX_C_SOURCE=200809L
40+
```
41+
42+
This enables POSIX.1-2008 features that rogueutil depends on (such as
43+
`usleep`, `nanosleep`, and terminal control functions). Without this flag,
44+
clang would fail to parse the header due to missing standard library
45+
declarations.
46+
47+
## Running the Example
48+
49+
```bash
50+
./generate-and-run.sh
51+
```
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
#!/usr/bin/env bash
2+
3+
# Exit on first error
4+
set -e
5+
6+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
7+
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
8+
9+
echo "# "
10+
echo "# Building rogueutil C bindings"
11+
echo "# "
12+
13+
cd "$SCRIPT_DIR/rogueutil"
14+
make
15+
16+
echo "# "
17+
echo "# Generating Haskell bindings"
18+
echo "# "
19+
20+
cd "$PROJECT_ROOT"
21+
22+
cabal run hs-bindgen-cli -- \
23+
preprocess \
24+
-I "$SCRIPT_DIR/rogueutil" \
25+
--hs-output-dir "$SCRIPT_DIR/hs-project/src" \
26+
--module RogueUtil.Generated \
27+
--clang-option=-D_POSIX_C_SOURCE=200809L \
28+
"$SCRIPT_DIR/rogueutil/rogueutil.h"
29+
30+
echo "# "
31+
echo "# Creating cabal.project.local"
32+
echo "# "
33+
34+
cat > "$SCRIPT_DIR/hs-project/cabal.project.local" <<EOF
35+
package c-rogueutil
36+
extra-include-dirs:
37+
$SCRIPT_DIR/rogueutil
38+
extra-lib-dirs:
39+
$SCRIPT_DIR/rogueutil
40+
shared: False
41+
42+
package hs-bindgen-runtime
43+
shared: True
44+
EOF
45+
46+
echo "# "
47+
echo "# Done!"
48+
echo "# "
49+
echo "Running the project"
50+
51+
cd $SCRIPT_DIR/hs-project
52+
export LD_LIBRARY_PATH=$SCRIPT_DIR/rogueutil/:\$LD_LIBRARY_PATH
53+
54+
cabal build
55+
cabal run c-rogueutil
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Revision history for c-rogueutil
2+
3+
## 0.1.0.0 -- YYYY-mm-dd
4+
5+
* First version. Released on an unsuspecting world.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
Copyright (c) 2024-2025, Well-Typed LLP and Anduril Industries Inc.
2+
3+
4+
Redistribution and use in source and binary forms, with or without
5+
modification, are permitted provided that the following conditions are met:
6+
7+
* Redistributions of source code must retain the above copyright
8+
notice, this list of conditions and the following disclaimer.
9+
10+
* Redistributions in binary form must reproduce the above
11+
copyright notice, this list of conditions and the following
12+
disclaimer in the documentation and/or other materials provided
13+
with the distribution.
14+
15+
* Neither the name of the copyright holder nor the names of its
16+
contributors may be used to endorse or promote products derived
17+
from this software without specific prior written permission.
18+
19+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20+
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21+
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22+
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23+
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24+
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25+
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26+
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27+
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
{-# LANGUAGE OverloadedStrings #-}
2+
3+
module Main where
4+
5+
import Control.Monad (forM_)
6+
import Foreign.C (newCString, withCString)
7+
8+
import RogueUtil.Generated qualified as RU
9+
import RogueUtil.Generated.Safe qualified as Safe
10+
11+
main :: IO ()
12+
main = do
13+
_ <- Safe.saveDefaultColor
14+
15+
Safe.cls
16+
Safe.hidecursor
17+
18+
Safe.setColor (fromIntegral $ RU.un_Color_code RU.YELLOW)
19+
Safe.setBackgroundColor (fromIntegral $ RU.un_Color_code RU.BLUE)
20+
titleStr <- newCString "=== RogueUtil Haskell Bindings Demo ==="
21+
Safe.printXY 20 2 (RU.RUTIL_STRING titleStr)
22+
Safe.resetColor
23+
24+
rows <- Safe.trows
25+
cols <- Safe.tcols
26+
Safe.setColor (fromIntegral $ RU.un_Color_code RU.CYAN)
27+
sizeMsg <- newCString $ "Terminal size: " ++ show cols ++ "x" ++ show rows
28+
Safe.printXY 20 4 (RU.RUTIL_STRING sizeMsg)
29+
Safe.resetColor
30+
31+
let colors = [RU.RED, RU.YELLOW, RU.GREEN, RU.CYAN, RU.BLUE, RU.MAGENTA]
32+
forM_ (zip [0..] colors) $ \(i, color) -> do
33+
Safe.setColor (fromIntegral $ RU.un_Color_code color)
34+
withCString ("█████ " ++ show color) $ \str ->
35+
Safe.printXY 20 (6 + fromIntegral i) (RU.RUTIL_STRING str)
36+
Safe.resetColor
37+
38+
Safe.locate 20 14
39+
Safe.showcursor
40+
pressKeyMsg <- newCString "Press any key to exit..."
41+
Safe.anykey (RU.RUTIL_STRING pressKeyMsg)
42+
43+
Safe.cls
44+
Safe.resetColor
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
cabal-version: 3.0
2+
-- The cabal-version field refers to the version of the .cabal specification,
3+
-- and can be different from the cabal-install (the tool) version and the
4+
-- Cabal (the library) version you are using. As such, the Cabal (the library)
5+
-- version used must be equal or greater than the version stated in this field.
6+
-- Starting from the specification version 2.2, the cabal-version field must be
7+
-- the first thing in the cabal file.
8+
9+
-- Initial package description 'c-rogueutil' generated by
10+
-- 'cabal init'. For further documentation, see:
11+
-- http://haskell.org/cabal/users-guide/
12+
--
13+
-- The name of the package.
14+
name: c-rogueutil
15+
16+
-- The package version.
17+
-- See the Haskell package versioning policy (PVP) for standards
18+
-- guiding when and how versions should be incremented.
19+
-- https://pvp.haskell.org
20+
-- PVP summary: +-+------- breaking API changes
21+
-- | | +----- non-breaking API additions
22+
-- | | | +--- code changes with no API change
23+
version: 0.1.0.0
24+
25+
-- A short (one-line) description of the package.
26+
-- synopsis:
27+
28+
-- A longer description of the package.
29+
-- description:
30+
31+
-- The license under which the package is released.
32+
license: BSD-3-Clause
33+
34+
-- The file containing the license text.
35+
license-file: LICENSE
36+
37+
-- The package author(s).
38+
author: Armando Santos
39+
40+
-- An email address to which users can send suggestions, bug reports, and patches.
41+
maintainer: [email protected]
42+
43+
-- A copyright notice.
44+
-- copyright:
45+
build-type: Simple
46+
47+
-- Extra doc files to be distributed with the package, such as a CHANGELOG or a README.
48+
extra-doc-files: CHANGELOG.md
49+
50+
-- Extra source files to be distributed with the package, such as examples, or a tutorial module.
51+
-- extra-source-files:
52+
53+
common warnings
54+
ghc-options: -Wall
55+
56+
library
57+
-- Import common warning flags.
58+
import: warnings
59+
60+
-- Modules exported by the library.
61+
exposed-modules: RogueUtil.Generated
62+
RogueUtil.Generated.Safe
63+
RogueUtil.Generated.Unsafe
64+
RogueUtil.Generated.Global
65+
RogueUtil.Generated.FunPtr
66+
67+
-- Modules included in this library but not exported.
68+
-- other-modules:
69+
70+
-- LANGUAGE extensions used by modules in this package.
71+
-- other-extensions:
72+
73+
-- Other library packages from which modules are imported.
74+
build-depends: base,
75+
hs-bindgen-runtime
76+
77+
-- Directories containing source files.
78+
hs-source-dirs: src
79+
80+
-- Base language which the package is written in.
81+
default-language: Haskell2010
82+
83+
default-extensions: ImportQualifiedPost
84+
85+
executable c-rogueutil
86+
-- Import common warning flags.
87+
import: warnings
88+
89+
-- .hs or .lhs file containing the Main module.
90+
main-is: Main.hs
91+
92+
-- Modules included in this executable, other than Main.
93+
-- other-modules:
94+
95+
-- LANGUAGE extensions used by modules in this package.
96+
-- other-extensions:
97+
98+
-- Other library packages from which modules are imported.
99+
build-depends:
100+
base,
101+
c-rogueutil
102+
103+
-- Directories containing source files.
104+
hs-source-dirs: app
105+
106+
-- Base language which the package is written in.
107+
default-language: Haskell2010
108+
109+
default-extensions: ImportQualifiedPost
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
packages: .
2+
../../../ansi-diff
3+
../../../hs-bindgen-runtime
4+
../../../c-expr-runtime
5+
../../../c-expr-dsl
6+
../../../hs-bindgen
7+
8+
source-repository-package
9+
type: git
10+
location: https://github.com/well-typed/libclang
11+
tag: d4ae9a5fcca9ae71fcb4df2c0b0ea37c6b15c48c

0 commit comments

Comments
 (0)