Skip to content

Commit 890973f

Browse files
committed
ci: make CI green — bump rust-ci to standards@8dc2bf0 (toolchain: stable fix); port ABI-FFI gate Python->Bash (Python is estate-banned)
Resolves the standing baseline CI reds (rust-ci toolchain error, governance Language/anti-pattern, governance workflow-lint) without altering the proven ABI. The Bash gate reproduces the former Python gate's verdict verbatim (validated across all -iser repos) and catches the same drift classes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A6PSzJWpRxtzGDjUCEh7Mx
1 parent a02f5a0 commit 890973f

4 files changed

Lines changed: 115 additions & 105 deletions

File tree

.github/workflows/abi-ffi-gate.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ jobs:
2222
steps:
2323
- uses: actions/checkout@v4
2424
- name: Run ABI-FFI gate
25-
run: python3 scripts/abi-ffi-gate.py
25+
run: bash scripts/abi-ffi-gate.sh
2626

2727
zig-build:
2828
name: Zig FFI builds + tests (Zig 0.14.0)

.github/workflows/rust-ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,4 @@ permissions:
1414

1515
jobs:
1616
rust-ci:
17-
uses: hyperpolymath/standards/.github/workflows/rust-ci-reusable.yml@d135b05bfc647d0c0fbfedc7e80f37ea50f49236
17+
uses: hyperpolymath/standards/.github/workflows/rust-ci-reusable.yml@8dc2bf039d1ff0372d650895c46bea7fbaec68ff

scripts/abi-ffi-gate.py

Lines changed: 0 additions & 103 deletions
This file was deleted.

scripts/abi-ffi-gate.sh

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
#!/usr/bin/env bash
2+
# SPDX-License-Identifier: MPL-2.0
3+
# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
4+
#
5+
# abi-ffi-gate.sh — fail (exit 1) if the Zig FFI does not conform to the Idris2
6+
# ABI. The Idris2 ABI is the source of truth. Bash port of the former
7+
# abi-ffi-gate.py (Python is banned estate-wide). No toolchain needed — only
8+
# coreutils + grep/awk/sed. Checks:
9+
#
10+
# 1. the Zig FFI carries no unrendered `{{...}}` template tokens;
11+
# 2. every `%foreign "C:<name>"` symbol declared anywhere in the ABI .idr
12+
# sources is exported by the Zig FFI (`export fn <name>`);
13+
# 3. the Zig `Result = enum(c_int)` and the Idris `resultToInt` agree on BOTH
14+
# names and integer values (the `Error`/`err` spelling is treated as one).
15+
#
16+
# Usage: bash scripts/abi-ffi-gate.sh [repo_root] (defaults to cwd)
17+
set -uo pipefail
18+
19+
root="${1:-.}"
20+
name="$(basename "$(cd "$root" 2>/dev/null && pwd || echo "$root")")"
21+
abi_dir="$root/src/interface/abi"
22+
zig_path="$root/src/interface/ffi/src/main.zig"
23+
24+
# canon(name): camelCase -> snake_case, lowercase, err -> error
25+
canon() {
26+
printf '%s' "$1" \
27+
| sed -E 's/([a-zA-Z0-9])([A-Z])/\1_\2/g' \
28+
| tr '[:upper:]' '[:lower:]' \
29+
| sed -E 's/^err$/error/'
30+
}
31+
32+
idr_files="$(find "$abi_dir" -name '*.idr' -not -path '*/build/*' 2>/dev/null | sort)"
33+
if [ -z "$idr_files" ]; then
34+
echo "ABI-FFI GATE: SKIP ($name) — no Idris2 ABI .idr files under $abi_dir"
35+
exit 0
36+
fi
37+
if [ ! -f "$zig_path" ]; then
38+
echo "ABI-FFI GATE: FAIL ($name) — no Zig FFI at $zig_path"
39+
exit 1
40+
fi
41+
42+
idr="$(cat $idr_files)"
43+
zig="$(cat "$zig_path")"
44+
errs=""
45+
46+
# 1. unrendered template tokens
47+
toks="$(printf '%s\n' "$zig" | grep -oE '\{\{[A-Za-z0-9_]+\}\}' | sort -u | tr '\n' ' ')"
48+
if [ -n "${toks// /}" ]; then
49+
errs="${errs} - Zig FFI has unrendered template tokens: ${toks}
50+
"
51+
fi
52+
53+
# 2. foreign C symbols must be exported
54+
csyms="$(printf '%s\n' "$idr" | grep -oE 'C:[A-Za-z0-9_]+' | sed 's/^C://' | sort -u | grep -v '^$')"
55+
exports="$(printf '%s\n' "$zig" | grep -oE 'export fn [A-Za-z0-9_]+' | awk '{print $3}' | sort -u | grep -v '^$')"
56+
missing="$(comm -23 <(printf '%s\n' "$csyms") <(printf '%s\n' "$exports") | tr '\n' ' ')"
57+
ncsyms="$(printf '%s\n' "$csyms" | grep -vc '^$' || true)"
58+
if [ -n "${missing// /}" ]; then
59+
errs="${errs} - ABI function(s) not exported by the Zig FFI: ${missing}
60+
"
61+
fi
62+
63+
# 3. result-code map (names + values) must agree
64+
idr_rc="$(printf '%s\n' "$idr" \
65+
| grep -oE 'resultToInt +[A-Za-z0-9]+ *= *[0-9]+' \
66+
| sed -E 's/resultToInt +([A-Za-z0-9]+) *= *([0-9]+)/\1 \2/' \
67+
| while read -r nm val; do echo "$(canon "$nm") $val"; done | sort -u)"
68+
nrc="$(printf '%s\n' "$idr_rc" | grep -vc '^$' || true)"
69+
70+
# Parse each `enum (c_int) { ... }` block separately (variants up to the first
71+
# `}`), tagged by a block id. Then in shell, canonicalise each block and pick
72+
# the one whose `ok == 0` with the most variants — mirrors Python find_result_enum.
73+
zig_raw="$(printf '%s\n' "$zig" | awk '
74+
/enum[ \t]*\([ \t]*c_int[ \t]*\)/ { cap=1; bid++ }
75+
cap {
76+
s=$0
77+
while (match(s, /@?"?[A-Za-z_][A-Za-z0-9_]*"?[ \t]*=[ \t]*[0-9]+/)) {
78+
seg=substr(s, RSTART, RLENGTH); s=substr(s, RSTART+RLENGTH)
79+
gsub(/[@"\t ]/,"",seg)
80+
eq=index(seg,"="); k=substr(seg,1,eq-1); v=substr(seg,eq+1)
81+
print bid, k, v
82+
}
83+
if ($0 ~ /\}/) cap=0
84+
}
85+
')"
86+
87+
zig_rc_final=""; best_n=-1
88+
for bid in $(printf '%s\n' "$zig_raw" | awk 'NF{print $1}' | sort -un); do
89+
cb="$(printf '%s\n' "$zig_raw" | awk -v b="$bid" '$1==b{print $2" "$3}' \
90+
| while read -r nm val; do [ -n "$nm" ] && echo "$(canon "$nm") $val"; done | sort -u)"
91+
if printf '%s\n' "$cb" | grep -qx 'ok 0'; then
92+
cnt="$(printf '%s\n' "$cb" | grep -vc '^$')"
93+
if [ "$cnt" -gt "$best_n" ]; then best_n="$cnt"; zig_rc_final="$cb"; fi
94+
fi
95+
done
96+
97+
if [ "$nrc" -gt 0 ] && [ -z "$zig_rc_final" ]; then
98+
errs="${errs} - no Zig enum(c_int) Result block (with ok = 0) found to compare result codes
99+
"
100+
elif [ "$nrc" -gt 0 ] && [ -n "$zig_rc_final" ] && [ "$idr_rc" != "$zig_rc_final" ]; then
101+
errs="${errs} - Result-code map differs (name or value):
102+
Idris resultToInt: $(printf '%s' "$idr_rc" | tr '\n' ',')
103+
Zig Result enum: $(printf '%s' "$zig_rc_final" | tr '\n' ',')
104+
"
105+
fi
106+
107+
if [ -n "$errs" ]; then
108+
echo "ABI-FFI GATE: FAIL ($name)"
109+
printf '%s' "$errs"
110+
exit 1
111+
fi
112+
echo "ABI-FFI GATE: OK ($name) — ${ncsyms} ABI functions exported, ${nrc} result codes match"
113+
exit 0

0 commit comments

Comments
 (0)