Skip to content

Commit d4bbc80

Browse files
feat(d-binding): stand up detachable CI + tests — binding-tier-1 (#158)
Replicates the detachable CI harness from the Chapel pilot for the D binding. - Setup bindings/d/ with Justfile, scripts, and tests. - Added D CI workflow with toolchain pinning (1.36.0). - Added forwarder recipes to root Justfile. - Closes #133.
1 parent 7a7ae97 commit d4bbc80

5 files changed

Lines changed: 490 additions & 0 deletions

File tree

.github/workflows/d-ci.yml

Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
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+
# D CI — proven binding-tier-1.
5+
#
6+
# This workflow follows the DETACHABLE-HARNESS contract for proven binding CI.
7+
# It references ONLY: bindings/d/, ffi/zig/, and bindings/c/include/proven.h.
8+
9+
name: D CI
10+
11+
permissions:
12+
contents: read
13+
14+
on:
15+
push:
16+
branches: [main]
17+
paths:
18+
- 'bindings/d/**'
19+
- 'ffi/zig/**'
20+
- 'bindings/c/include/proven.h'
21+
- '.github/workflows/d-ci.yml'
22+
pull_request:
23+
branches: [main]
24+
paths:
25+
- 'bindings/d/**'
26+
- 'ffi/zig/**'
27+
- 'bindings/c/include/proven.h'
28+
- '.github/workflows/d-ci.yml'
29+
workflow_dispatch:
30+
31+
concurrency:
32+
group: d-ci-${{ github.ref }}
33+
cancel-in-progress: true
34+
35+
env:
36+
LDC_VERSION: '1.36.0'
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/d-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/d/*|ffi/zig/*|bindings/c/include|bindings/c/include/proven.h|.github/workflows/d-ci.yml)
59+
;;
60+
bindings/*/*)
61+
echo "::error file=.github/workflows/d-ci.yml::detachability violation: $p"
62+
forbidden=1
63+
;;
64+
tests/*)
65+
echo "::error file=.github/workflows/d-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/d-ci.yml | sort -u)
71+
[ "$forbidden" -eq 0 ]
72+
echo "detachability-guard: OK"
73+
74+
d-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+
105+
- name: Setup D compiler (LDC)
106+
uses: dlang-community/setup-dlang@4071ba822f6d0f622ba7359556d11f5827725916 # v1.6.0
107+
with:
108+
compiler: ldc-${{ env.LDC_VERSION }}
109+
110+
- name: D build
111+
working-directory: proven/bindings/d
112+
env:
113+
PROVEN_LIB_PATH: ${{ github.workspace }}/proven/ffi/zig/zig-out/lib
114+
run: |
115+
set -euo pipefail
116+
dub build
117+
118+
- name: Upload libproven.so artefact
119+
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4
120+
with:
121+
name: libproven-x86_64-linux-gnu
122+
path: proven/ffi/zig/zig-out/lib/libproven.so
123+
if-no-files-found: error
124+
retention-days: 1
125+
126+
d-symbol-audit:
127+
runs-on: ubuntu-22.04
128+
needs: d-build
129+
timeout-minutes: 5
130+
steps:
131+
- name: Checkout (binding only)
132+
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
133+
with:
134+
sparse-checkout: |
135+
bindings/d
136+
sparse-checkout-cone-mode: false
137+
138+
- name: Download libproven.so
139+
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4
140+
with:
141+
name: libproven-x86_64-linux-gnu
142+
path: lib/
143+
144+
- name: Run d-symbol-audit
145+
working-directory: bindings/d
146+
env:
147+
PROVEN_LIB_PATH: ${{ github.workspace }}/lib
148+
run: ./scripts/symbol-audit.sh
149+
150+
d-smoke:
151+
runs-on: ubuntu-22.04
152+
needs: d-build
153+
timeout-minutes: 10
154+
steps:
155+
- name: Checkout (binding + C header only)
156+
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
157+
with:
158+
sparse-checkout: |
159+
bindings/d
160+
bindings/c/include
161+
.github/workflows/d-ci.yml
162+
sparse-checkout-cone-mode: false
163+
164+
- name: Setup D compiler (LDC)
165+
uses: dlang-community/setup-dlang@4071ba822f6d0f622ba7359556d11f5827725916 # v1.6.0
166+
with:
167+
compiler: ldc-${{ env.LDC_VERSION }}
168+
169+
- name: Install just
170+
uses: extractions/setup-just@dd310ad5a97d8e7b41793f8ef055398d51ad4de6 # v2
171+
with:
172+
just-version: '1.36.0'
173+
174+
- name: Download libproven.so
175+
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4
176+
with:
177+
name: libproven-x86_64-linux-gnu
178+
path: lib/
179+
180+
- name: Build + Run hello_smoke
181+
working-directory: bindings/d
182+
env:
183+
PROVEN_LIB_PATH: ${{ github.workspace }}/lib
184+
PROVEN_INCLUDE_PATH: ${{ github.workspace }}/bindings/c/include
185+
LD_LIBRARY_PATH: ${{ github.workspace }}/lib
186+
run: |
187+
ldc2 -of=smoke/hello_smoke \
188+
source/proven/*.d smoke/hello_smoke.d \
189+
-L-L$PROVEN_LIB_PATH -L-lproven \
190+
-L-rpath=$PROVEN_LIB_PATH
191+
./smoke/hello_smoke
192+
193+
d-tests:
194+
runs-on: ubuntu-22.04
195+
needs: d-build
196+
timeout-minutes: 15
197+
steps:
198+
- name: Checkout (binding + C header only)
199+
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
200+
with:
201+
sparse-checkout: |
202+
bindings/d
203+
bindings/c/include
204+
.github/workflows/d-ci.yml
205+
sparse-checkout-cone-mode: false
206+
207+
- name: Setup D compiler (LDC)
208+
uses: dlang-community/setup-dlang@4071ba822f6d0f622ba7359556d11f5827725916 # v1.6.0
209+
with:
210+
compiler: ldc-${{ env.LDC_VERSION }}
211+
212+
- name: Install just
213+
uses: extractions/setup-just@dd310ad5a97d8e7b41793f8ef055398d51ad4de6 # v2
214+
with:
215+
just-version: '1.36.0'
216+
217+
- name: Download libproven.so
218+
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4
219+
with:
220+
name: libproven-x86_64-linux-gnu
221+
path: lib/
222+
223+
- name: Run all D tests
224+
working-directory: bindings/d
225+
env:
226+
PROVEN_LIB_PATH: ${{ github.workspace }}/lib
227+
PROVEN_INCLUDE_PATH: ${{ github.workspace }}/bindings/c/include
228+
LD_LIBRARY_PATH: ${{ github.workspace }}/lib
229+
run: dub test
230+
231+
d-detachable-build:
232+
runs-on: ubuntu-22.04
233+
needs: d-build
234+
timeout-minutes: 15
235+
steps:
236+
- name: Sparse checkout (detachable set only)
237+
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
238+
with:
239+
sparse-checkout: |
240+
bindings/d
241+
bindings/c/include/proven.h
242+
.github/workflows/d-ci.yml
243+
sparse-checkout-cone-mode: false
244+
path: proven-d
245+
246+
- name: Verify detached layout (no other proven trees present)
247+
working-directory: proven-d
248+
run: |
249+
set -euo pipefail
250+
if [ -d "src" ] || [ -d "ffi" ] || [ -d "tests" ] || [ -d "audits" ]; then
251+
echo "::error::detachability violation: non-binding trees present in detached checkout"
252+
ls -la
253+
exit 1
254+
fi
255+
test -d bindings/d

bindings/d/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-d binding — detachable build harness.
5+
#
6+
# This Justfile contains every recipe needed to build, audit, and test the
7+
# D 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-d checkout (PROVEN_LIB_PATH=<external path>)
16+
#
17+
# dub.json is the package-metadata source of truth (ldc2Version range,
18+
# license, dependencies). The actual compile uses `ldc2` 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 D modules without producing an executable. Mason is
42+
# used here as a lightweight metadata + ldc2Version check; the actual
43+
# binding build is performed by the build / test recipes via ldc2.
44+
mason-check:
45+
@echo "[d] mason check (ldc2Version 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+
ldc2 -o smoke/hello_smoke \
53+
smoke/hello_smoke.ldc2 \
54+
-M src/ \
55+
-I "{{PROVEN_INCLUDE_PATH}}" \
56+
-L "$PROVEN_LIB_PATH" -lproven \
57+
--ldflags "-Wl,-rpath,$PROVEN_LIB_PATH"
58+
echo "[d] 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 "[d] Building hello_smoke..."
68+
ldc2 -o smoke/hello_smoke smoke/hello_smoke.ldc2 \
69+
-M src/ -I "$INC" -L "$LIB" -lproven \
70+
--ldflags "-Wl,-rpath,$LIB"
71+
echo "[d] === hello_smoke ==="
72+
./smoke/hello_smoke
73+
for t in tests/Test*.ldc2; do
74+
name="$(basename "${t%.ldc2}")"
75+
echo "[d] === $name ==="
76+
ldc2 -o "tests/$name" "$t" \
77+
-M src/ -I "$INC" -L "$LIB" -lproven \
78+
--ldflags "-Wl,-rpath,$LIB"
79+
"./tests/$name"
80+
done
81+
echo "[d] All D 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 '*.ldc2' -delete 2>/dev/null || true
89+
find . -name '*.tmp' -delete 2>/dev/null || true
90+
echo "[d] Clean complete."

0 commit comments

Comments
 (0)