Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
255 changes: 255 additions & 0 deletions .github/workflows/d-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
# SPDX-License-Identifier: MPL-2.0
# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
#
# D CI — proven binding-tier-1.
#
# This workflow follows the DETACHABLE-HARNESS contract for proven binding CI.
# It references ONLY: bindings/d/, ffi/zig/, and bindings/c/include/proven.h.

name: D CI

permissions:
contents: read

on:
push:
branches: [main]
paths:
- 'bindings/d/**'
- 'ffi/zig/**'
- 'bindings/c/include/proven.h'
- '.github/workflows/d-ci.yml'
pull_request:
branches: [main]
paths:
- 'bindings/d/**'
- 'ffi/zig/**'
- 'bindings/c/include/proven.h'
- '.github/workflows/d-ci.yml'
workflow_dispatch:

concurrency:
group: d-ci-${{ github.ref }}
cancel-in-progress: true

env:
LDC_VERSION: '1.36.0'
ZIG_VERSION: '0.15.2'

jobs:
detachability-guard:
runs-on: ubuntu-22.04
timeout-minutes: 2
steps:
- name: Checkout (workflow file only)
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
with:
sparse-checkout: |
.github/workflows/d-ci.yml
sparse-checkout-cone-mode: false

- name: Detachability grep guard
run: |
set -euo pipefail
forbidden=0
while IFS= read -r raw; do
p="${raw%[.,;:)]}"
case "$p" in
bindings/d/*|ffi/zig/*|bindings/c/include|bindings/c/include/proven.h|.github/workflows/d-ci.yml)
;;
bindings/*/*)
echo "::error file=.github/workflows/d-ci.yml::detachability violation: $p"
forbidden=1
;;
tests/*)
echo "::error file=.github/workflows/d-ci.yml::detachability violation (root-level tests/): $p"
forbidden=1
;;
esac
done < <(grep -oE '(bindings/[a-zA-Z0-9_-]+/[A-Za-z0-9_./-]*|tests/[A-Za-z0-9_./-]+)' \
.github/workflows/d-ci.yml | sort -u)
[ "$forbidden" -eq 0 ]
echo "detachability-guard: OK"

d-build:
runs-on: ubuntu-22.04
needs: detachability-guard
timeout-minutes: 20

steps:
- name: Checkout proven
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
with:
path: proven

- name: Setup Zig ${{ env.ZIG_VERSION }}
uses: mlugg/setup-zig@e7d1537c378b83b8049f65dda471d87a2f7b2df2 # v1
with:
version: ${{ env.ZIG_VERSION }}

- name: Build libproven.so (WIRED-subset fixture)
working-directory: proven/ffi/zig
run: |
set -euo pipefail
mkdir -p zig-out/lib
zig build-lib -dynamic -O ReleaseSafe \
-fPIC \
--name proven \
-femit-bin=zig-out/lib/libproven.so \
src/main.zig -lc
nm -D --defined-only zig-out/lib/libproven.so \
| awk '$2 ~ /^[TWV]$/ { print $3 }' \
| grep -E '^(proven_path_has_traversal|proven_header_has_crlf|proven_free_string|proven_version|proven_build_info)$' \
| sort -u | tee /tmp/wired-exports.txt

- name: Setup D compiler (LDC)
uses: dlang-community/setup-dlang@4071ba822f6d0f622ba7359556d11f5827725916 # v1.6.0
with:
compiler: ldc-${{ env.LDC_VERSION }}

- name: D build
working-directory: proven/bindings/d
env:
PROVEN_LIB_PATH: ${{ github.workspace }}/proven/ffi/zig/zig-out/lib
run: |
set -euo pipefail
dub build

- name: Upload libproven.so artefact
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4
with:
name: libproven-x86_64-linux-gnu
path: proven/ffi/zig/zig-out/lib/libproven.so
if-no-files-found: error
retention-days: 1

d-symbol-audit:
runs-on: ubuntu-22.04
needs: d-build
timeout-minutes: 5
steps:
- name: Checkout (binding only)
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
with:
sparse-checkout: |
bindings/d
sparse-checkout-cone-mode: false

- name: Download libproven.so
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4
with:
name: libproven-x86_64-linux-gnu
path: lib/

- name: Run d-symbol-audit
working-directory: bindings/d
env:
PROVEN_LIB_PATH: ${{ github.workspace }}/lib
run: ./scripts/symbol-audit.sh

d-smoke:
runs-on: ubuntu-22.04
needs: d-build
timeout-minutes: 10
steps:
- name: Checkout (binding + C header only)
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
with:
sparse-checkout: |
bindings/d
bindings/c/include
.github/workflows/d-ci.yml
sparse-checkout-cone-mode: false

- name: Setup D compiler (LDC)
uses: dlang-community/setup-dlang@4071ba822f6d0f622ba7359556d11f5827725916 # v1.6.0
with:
compiler: ldc-${{ env.LDC_VERSION }}

- name: Install just
uses: extractions/setup-just@dd310ad5a97d8e7b41793f8ef055398d51ad4de6 # v2
with:
just-version: '1.36.0'

- name: Download libproven.so
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4
with:
name: libproven-x86_64-linux-gnu
path: lib/

- name: Build + Run hello_smoke
working-directory: bindings/d
env:
PROVEN_LIB_PATH: ${{ github.workspace }}/lib
PROVEN_INCLUDE_PATH: ${{ github.workspace }}/bindings/c/include
LD_LIBRARY_PATH: ${{ github.workspace }}/lib
run: |
ldc2 -of=smoke/hello_smoke \
source/proven/*.d smoke/hello_smoke.d \
-L-L$PROVEN_LIB_PATH -L-lproven \
-L-rpath=$PROVEN_LIB_PATH
./smoke/hello_smoke

d-tests:
runs-on: ubuntu-22.04
needs: d-build
timeout-minutes: 15
steps:
- name: Checkout (binding + C header only)
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
with:
sparse-checkout: |
bindings/d
bindings/c/include
.github/workflows/d-ci.yml
sparse-checkout-cone-mode: false

- name: Setup D compiler (LDC)
uses: dlang-community/setup-dlang@4071ba822f6d0f622ba7359556d11f5827725916 # v1.6.0
with:
compiler: ldc-${{ env.LDC_VERSION }}

- name: Install just
uses: extractions/setup-just@dd310ad5a97d8e7b41793f8ef055398d51ad4de6 # v2
with:
just-version: '1.36.0'

- name: Download libproven.so
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4
with:
name: libproven-x86_64-linux-gnu
path: lib/

- name: Run all D tests
working-directory: bindings/d
env:
PROVEN_LIB_PATH: ${{ github.workspace }}/lib
PROVEN_INCLUDE_PATH: ${{ github.workspace }}/bindings/c/include
LD_LIBRARY_PATH: ${{ github.workspace }}/lib
run: dub test

d-detachable-build:
runs-on: ubuntu-22.04
needs: d-build
timeout-minutes: 15
steps:
- name: Sparse checkout (detachable set only)
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
with:
sparse-checkout: |
bindings/d
bindings/c/include/proven.h
.github/workflows/d-ci.yml
sparse-checkout-cone-mode: false
path: proven-d

- name: Verify detached layout (no other proven trees present)
working-directory: proven-d
run: |
set -euo pipefail
if [ -d "src" ] || [ -d "ffi" ] || [ -d "tests" ] || [ -d "audits" ]; then
echo "::error::detachability violation: non-binding trees present in detached checkout"
ls -la
exit 1
fi
test -d bindings/d
90 changes: 90 additions & 0 deletions bindings/d/Justfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# SPDX-License-Identifier: MPL-2.0
# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
#
# proven-d binding — detachable build harness.
#
# This Justfile contains every recipe needed to build, audit, and test the
# D binding. It is self-contained: it MUST NOT reference recipes
# from the proven repo's root Justfile, nor reach outside the binding's
# directory tree. The libproven.so artefact is supplied via the
# PROVEN_LIB_PATH environment variable so that this harness works in three
# layouts:
#
# 1. in-tree (PROVEN_LIB_PATH=../../ffi/zig/zig-out/lib)
# 2. installed (PROVEN_LIB_PATH=/usr/local/lib)
# 3. standalone proven-d checkout (PROVEN_LIB_PATH=<external path>)
#
# dub.json is the package-metadata source of truth (ldc2Version range,
# license, dependencies). The actual compile uses `ldc2` directly because
# Mason does not currently support C-library linking declaratively; see
# the ADR at docs/adr/0001-binding-ci-template.md for the rationale.

# Source of truth for the C ABI header. This is supplied via env so the
# harness can be extracted to a standalone repo carrying its own copy of
# proven.h (e.g. vendored under include/).
PROVEN_INCLUDE_PATH := env_var_or_default("PROVEN_INCLUDE_PATH", justfile_directory() + "/../c/include")

default:
@just --list

# Fast pre-build gate: confirm libproven.so exports every WIRED symbol in
# symbol-manifest.txt. Runs in < 5s; no compilation.
check:
#!/usr/bin/env bash
set -euo pipefail
if [ -z "${PROVEN_LIB_PATH:-}" ]; then
echo "ERROR: set PROVEN_LIB_PATH to the directory containing libproven.so" >&2
exit 2
fi
./scripts/symbol-audit.sh

# Typecheck the D modules without producing an executable. Mason is
# used here as a lightweight metadata + ldc2Version check; the actual
# binding build is performed by the build / test recipes via ldc2.
mason-check:
@echo "[d] mason check (ldc2Version gate, no link)..."
mason build --no-rebuild || mason build

# Build the smoke target against PROVEN_LIB_PATH/libproven.so.
build: check
#!/usr/bin/env bash
set -euo pipefail
ldc2 -o smoke/hello_smoke \
smoke/hello_smoke.ldc2 \
-M src/ \
-I "{{PROVEN_INCLUDE_PATH}}" \
-L "$PROVEN_LIB_PATH" -lproven \
--ldflags "-Wl,-rpath,$PROVEN_LIB_PATH"
echo "[d] hello_smoke built: smoke/hello_smoke"

# Build + run smoke + every WIRED per-module test in tests/.
# Fails on the first red test.
test: check
#!/usr/bin/env bash
set -euo pipefail
INC="{{PROVEN_INCLUDE_PATH}}"
LIB="$PROVEN_LIB_PATH"
echo "[d] Building hello_smoke..."
ldc2 -o smoke/hello_smoke smoke/hello_smoke.ldc2 \
-M src/ -I "$INC" -L "$LIB" -lproven \
--ldflags "-Wl,-rpath,$LIB"
echo "[d] === hello_smoke ==="
./smoke/hello_smoke
for t in tests/Test*.ldc2; do
name="$(basename "${t%.ldc2}")"
echo "[d] === $name ==="
ldc2 -o "tests/$name" "$t" \
-M src/ -I "$INC" -L "$LIB" -lproven \
--ldflags "-Wl,-rpath,$LIB"
"./tests/$name"
done
echo "[d] All D tests passed."

# Clean compiled binaries; leaves sources untouched.
clean:
#!/usr/bin/env bash
set -euo pipefail
rm -f smoke/hello_smoke
find tests -maxdepth 1 -type f -perm -u+x ! -name '*.ldc2' -delete 2>/dev/null || true
find . -name '*.tmp' -delete 2>/dev/null || true
echo "[d] Clean complete."
Loading
Loading