Skip to content

Commit 335a256

Browse files
Merge branch 'main' into feat/crystal-binding-standup
2 parents 11971a0 + 840a10e commit 335a256

5 files changed

Lines changed: 459 additions & 0 deletions

File tree

.github/workflows/ocaml-ci.yml

Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
# SPDX-License-Identifier: MPL-2.0
2+
# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
3+
#
4+
# OCaml CI — proven binding-tier-1.
5+
#
6+
# This workflow follows the DETACHABLE-HARNESS contract for proven binding CI.
7+
# It references ONLY: bindings/ocaml/, ffi/zig/, and bindings/c/include/proven.h.
8+
9+
name: OCaml CI
10+
11+
permissions:
12+
contents: read
13+
14+
on:
15+
push:
16+
branches: [main]
17+
paths:
18+
- 'bindings/ocaml/**'
19+
- 'ffi/zig/**'
20+
- 'bindings/c/include/proven.h'
21+
- '.github/workflows/ocaml-ci.yml'
22+
pull_request:
23+
branches: [main]
24+
paths:
25+
- 'bindings/ocaml/**'
26+
- 'ffi/zig/**'
27+
- 'bindings/c/include/proven.h'
28+
- '.github/workflows/ocaml-ci.yml'
29+
workflow_dispatch:
30+
31+
concurrency:
32+
group: ocaml-ci-${{ github.ref }}
33+
cancel-in-progress: true
34+
35+
env:
36+
OCAML_VERSION: '5.1.1'
37+
ZIG_VERSION: '0.15.2'
38+
39+
jobs:
40+
detachability-guard:
41+
runs-on: ubuntu-22.04
42+
timeout-minutes: 2
43+
steps:
44+
- name: Checkout (workflow file only)
45+
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
46+
with:
47+
sparse-checkout: |
48+
.github/workflows/ocaml-ci.yml
49+
sparse-checkout-cone-mode: false
50+
51+
- name: Detachability grep guard
52+
run: |
53+
set -euo pipefail
54+
forbidden=0
55+
while IFS= read -r raw; do
56+
p="${raw%[.,;:)]}"
57+
case "$p" in
58+
bindings/ocaml/*|ffi/zig/*|bindings/c/include|bindings/c/include/proven.h|.github/workflows/ocaml-ci.yml)
59+
;;
60+
bindings/*/*)
61+
echo "::error file=.github/workflows/ocaml-ci.yml::detachability violation: $p"
62+
forbidden=1
63+
;;
64+
tests/*)
65+
echo "::error file=.github/workflows/ocaml-ci.yml::detachability violation (root-level tests/): $p"
66+
forbidden=1
67+
;;
68+
esac
69+
done < <(grep -oE '(bindings/[a-zA-Z0-9_-]+/[A-Za-z0-9_./-]*|tests/[A-Za-z0-9_./-]+)' \
70+
.github/workflows/ocaml-ci.yml | sort -u)
71+
[ "$forbidden" -eq 0 ]
72+
echo "detachability-guard: OK"
73+
74+
ocaml-build:
75+
runs-on: ubuntu-22.04
76+
needs: detachability-guard
77+
timeout-minutes: 20
78+
79+
steps:
80+
- name: Checkout proven
81+
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
82+
with:
83+
path: proven
84+
85+
- name: Setup Zig ${{ env.ZIG_VERSION }}
86+
uses: mlugg/setup-zig@e7d1537c378b83b8049f65dda471d87a2f7b2df2 # v1
87+
with:
88+
version: ${{ env.ZIG_VERSION }}
89+
90+
- name: Build libproven.so (WIRED-subset fixture)
91+
working-directory: proven/ffi/zig
92+
run: |
93+
set -euo pipefail
94+
mkdir -p zig-out/lib
95+
zig build-lib -dynamic -O ReleaseSafe \
96+
-fPIC \
97+
--name proven \
98+
-femit-bin=zig-out/lib/libproven.so \
99+
src/main.zig -lc
100+
nm -D --defined-only zig-out/lib/libproven.so \
101+
| awk '$2 ~ /^[TWV]$/ { print $3 }' \
102+
| grep -E '^(proven_path_has_traversal|proven_header_has_crlf|proven_free_string|proven_version|proven_build_info)$' \
103+
| sort -u | tee /tmp/wired-exports.txt
104+
# Wait! OCaml might need more for the full lib integration
105+
# but for smoke we just need these.
106+
107+
- name: Setup OCaml ${{ env.OCAML_VERSION }}
108+
uses: ocaml/setup-ocaml@4462cc269f8d5e869a19c676450637119e71ec60 # v3
109+
with:
110+
ocaml-compiler: ${{ env.OCAML_VERSION }}
111+
dune-cache: true
112+
113+
- name: Install dependencies
114+
run: opam install dune ctypes ctypes-foreign
115+
116+
- name: OCaml build
117+
working-directory: proven/bindings/ocaml
118+
env:
119+
PROVEN_LIB_PATH: ${{ github.workspace }}/proven/ffi/zig/zig-out/lib
120+
run: |
121+
set -euo pipefail
122+
dune build
123+
124+
- name: Upload libproven.so artefact
125+
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4
126+
with:
127+
name: libproven-x86_64-linux-gnu
128+
path: proven/ffi/zig/zig-out/lib/libproven.so
129+
if-no-files-found: error
130+
retention-days: 1
131+
132+
ocaml-symbol-audit:
133+
runs-on: ubuntu-22.04
134+
needs: ocaml-build
135+
timeout-minutes: 5
136+
steps:
137+
- name: Checkout (binding only)
138+
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
139+
with:
140+
sparse-checkout: |
141+
bindings/ocaml
142+
sparse-checkout-cone-mode: false
143+
144+
- name: Download libproven.so
145+
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4
146+
with:
147+
name: libproven-x86_64-linux-gnu
148+
path: lib/
149+
150+
- name: Run ocaml-symbol-audit
151+
working-directory: bindings/ocaml
152+
env:
153+
PROVEN_LIB_PATH: ${{ github.workspace }}/lib
154+
run: ./scripts/symbol-audit.sh
155+
156+
ocaml-smoke:
157+
runs-on: ubuntu-22.04
158+
needs: ocaml-build
159+
timeout-minutes: 10
160+
steps:
161+
- name: Checkout (binding + C header only)
162+
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
163+
with:
164+
sparse-checkout: |
165+
bindings/ocaml
166+
bindings/c/include
167+
.github/workflows/ocaml-ci.yml
168+
sparse-checkout-cone-mode: false
169+
170+
- name: Setup OCaml ${{ env.OCAML_VERSION }}
171+
uses: ocaml/setup-ocaml@4462cc269f8d5e869a19c676450637119e71ec60 # v3
172+
with:
173+
ocaml-compiler: ${{ env.OCAML_VERSION }}
174+
175+
- name: Install dependencies
176+
run: opam install dune ctypes ctypes-foreign
177+
178+
- name: Install just
179+
uses: extractions/setup-just@dd310ad5a97d8e7b41793f8ef055398d51ad4de6 # v2
180+
with:
181+
just-version: '1.36.0'
182+
183+
- name: Download libproven.so
184+
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4
185+
with:
186+
name: libproven-x86_64-linux-gnu
187+
path: lib/
188+
189+
- name: Build + Run hello_smoke
190+
working-directory: bindings/ocaml
191+
env:
192+
PROVEN_LIB_PATH: ${{ github.workspace }}/lib
193+
PROVEN_INCLUDE_PATH: ${{ github.workspace }}/bindings/c/include
194+
LD_LIBRARY_PATH: ${{ github.workspace }}/lib
195+
run: |
196+
dune build smoke/hello_smoke.exe
197+
./_build/default/smoke/hello_smoke.exe
198+
199+
ocaml-detachable-build:
200+
runs-on: ubuntu-22.04
201+
needs: ocaml-build
202+
timeout-minutes: 15
203+
steps:
204+
- name: Sparse checkout (detachable set only)
205+
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
206+
with:
207+
sparse-checkout: |
208+
bindings/ocaml
209+
bindings/c/include/proven.h
210+
.github/workflows/ocaml-ci.yml
211+
sparse-checkout-cone-mode: false
212+
path: proven-ocaml
213+
214+
- name: Verify detached layout (no other proven trees present)
215+
working-directory: proven-ocaml
216+
run: |
217+
set -euo pipefail
218+
if [ -d "src" ] || [ -d "ffi" ] || [ -d "tests" ] || [ -d "audits" ]; then
219+
echo "::error::detachability violation: non-binding trees present in detached checkout"
220+
ls -la
221+
exit 1
222+
fi
223+
test -d bindings/ocaml

bindings/ocaml/Justfile

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# SPDX-License-Identifier: MPL-2.0
2+
# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
3+
#
4+
# proven-ocaml binding — detachable build harness.
5+
#
6+
# This Justfile contains every recipe needed to build, audit, and test the
7+
# OCaml binding. It is self-contained: it MUST NOT reference recipes
8+
# from the proven repo's root Justfile, nor reach outside the binding's
9+
# directory tree. The libproven.so artefact is supplied via the
10+
# PROVEN_LIB_PATH environment variable so that this harness works in three
11+
# layouts:
12+
#
13+
# 1. in-tree (PROVEN_LIB_PATH=../../ffi/zig/zig-out/lib)
14+
# 2. installed (PROVEN_LIB_PATH=/usr/local/lib)
15+
# 3. standalone proven-ocaml checkout (PROVEN_LIB_PATH=<external path>)
16+
#
17+
# dune-project is the package-metadata source of truth (ocamlVersion range,
18+
# license, dependencies). The actual compile uses `ocaml` directly because
19+
# Mason does not currently support C-library linking declaratively; see
20+
# the ADR at docs/adr/0001-binding-ci-template.md for the rationale.
21+
22+
# Source of truth for the C ABI header. This is supplied via env so the
23+
# harness can be extracted to a standalone repo carrying its own copy of
24+
# proven.h (e.g. vendored under include/).
25+
PROVEN_INCLUDE_PATH := env_var_or_default("PROVEN_INCLUDE_PATH", justfile_directory() + "/../c/include")
26+
27+
default:
28+
@just --list
29+
30+
# Fast pre-build gate: confirm libproven.so exports every WIRED symbol in
31+
# symbol-manifest.txt. Runs in < 5s; no compilation.
32+
check:
33+
#!/usr/bin/env bash
34+
set -euo pipefail
35+
if [ -z "${PROVEN_LIB_PATH:-}" ]; then
36+
echo "ERROR: set PROVEN_LIB_PATH to the directory containing libproven.so" >&2
37+
exit 2
38+
fi
39+
./scripts/symbol-audit.sh
40+
41+
# Typecheck the OCaml modules without producing an executable. Mason is
42+
# used here as a lightweight metadata + ocamlVersion check; the actual
43+
# binding build is performed by the build / test recipes via ocaml.
44+
mason-check:
45+
@echo "[ocaml] mason check (ocamlVersion gate, no link)..."
46+
mason build --no-rebuild || mason build
47+
48+
# Build the smoke target against PROVEN_LIB_PATH/libproven.so.
49+
build: check
50+
#!/usr/bin/env bash
51+
set -euo pipefail
52+
ocaml -o smoke/hello_smoke \
53+
smoke/hello_smoke.ocaml \
54+
-M src/ \
55+
-I "{{PROVEN_INCLUDE_PATH}}" \
56+
-L "$PROVEN_LIB_PATH" -lproven \
57+
--ldflags "-Wl,-rpath,$PROVEN_LIB_PATH"
58+
echo "[ocaml] hello_smoke built: smoke/hello_smoke"
59+
60+
# Build + run smoke + every WIRED per-module test in tests/.
61+
# Fails on the first red test.
62+
test: check
63+
#!/usr/bin/env bash
64+
set -euo pipefail
65+
INC="{{PROVEN_INCLUDE_PATH}}"
66+
LIB="$PROVEN_LIB_PATH"
67+
echo "[ocaml] Building hello_smoke..."
68+
ocaml -o smoke/hello_smoke smoke/hello_smoke.ocaml \
69+
-M src/ -I "$INC" -L "$LIB" -lproven \
70+
--ldflags "-Wl,-rpath,$LIB"
71+
echo "[ocaml] === hello_smoke ==="
72+
./smoke/hello_smoke
73+
for t in tests/Test*.ocaml; do
74+
name="$(basename "${t%.ocaml}")"
75+
echo "[ocaml] === $name ==="
76+
ocaml -o "tests/$name" "$t" \
77+
-M src/ -I "$INC" -L "$LIB" -lproven \
78+
--ldflags "-Wl,-rpath,$LIB"
79+
"./tests/$name"
80+
done
81+
echo "[ocaml] All OCaml tests passed."
82+
83+
# Clean compiled binaries; leaves sources untouched.
84+
clean:
85+
#!/usr/bin/env bash
86+
set -euo pipefail
87+
rm -f smoke/hello_smoke
88+
find tests -maxdepth 1 -type f -perm -u+x ! -name '*.ocaml' -delete 2>/dev/null || true
89+
find . -name '*.tmp' -delete 2>/dev/null || true
90+
echo "[ocaml] Clean complete."

0 commit comments

Comments
 (0)