flan/.github/workflows/checks.yml
Joseph Ferano 16498a4e60 The alias that nobody ran now has something that runs it
A GitHub Actions workflow on push: dune build, dune test --force, dune build
@checks. @x86 parity is not under dune test, so the routine suite never
protected it; both of this repository's silent failures would have been caught
by one person typing one command, and the problem was never the command.

The suite step keeps its log and greps it for Fatal error, because a suite
that passes while leaving an unhandled exception on stderr is one that is
telling you something and being ignored.

FLAN_LLC is pinned to the llc matching clang's version rather than left to
PATH order: the live loop goes llc + ld -shared + dlopen and never calls the
clang driver, so a mismatch breaks every reload test while flan build keeps
working, which is a bad failure to debug from a log.

What an Ubuntu runner cannot cover -- raylib by exact Fedora soname,
emscripten, a wasi sysroot, lldb -- is written in the workflow with the skip
path each one already takes, so the tick does not read as more than it is.
README.md and test/dune both said there was no CI; both now say what there is
and what it misses.
2026-09-17 22:11:25 +07:00

149 lines
6.6 KiB
YAML

# The thing README.md and test/dune both said did not exist.
#
# `dune build @checks` on every push, which is the whole job. Both of this
# repository's silent failures — two x86 refusals that sat for a month, a page
# of examples that stopped compiling for two days — would have been caught by
# one person typing one command, and the argument for CI here is not process:
# it is that nobody typed it. @x86 parity in particular is *not* under
# `dune test`, so the routine suite does not protect it.
#
# `dune test` stays what it is: seconds, run constantly, unchanged by this
# file. The workflow adds a caller, not an alias.
#
# ── What this does not cover, and why ────────────────────────────────────
#
# Stated plainly, because a green tick that quietly checks less than it looks
# like it does is worse than no tick.
#
# raylib. vendor/raylib/link names `-l:libraylib.so.550` by exact soname,
# which is a Fedora spelling; Ubuntu ships neither that soname nor 5.5.
# So every raylib program is skipped here, on three separate paths that
# all already existed: the acceptance cases probe with `ldconfig -p |
# grep libraylib` and print a skip line; survey.sh records a program that
# will not link as `does-not-compile`, which SURVEY_STRICT deliberately
# does not fail on; and web/examples/check.sh skips shimdemo.flan by name.
# Nothing had to be weakened to make this pass — but the graphics half of
# the corpus is unchecked here and is checked only on a machine with
# raylib installed.
#
# emscripten and the web target. test_web probes for emcc and a raylib
# archive built by vendor/raylib/build-web.sh and skips with the reason.
# Installing an emsdk per run is minutes for a target NEXT.md has
# deprioritised, so it is left out on purpose.
#
# wasm32-wasi. Needs a wasi-libc sysroot and a builtins archive that no
# Ubuntu package supplies. The acceptance case probes by building and
# running the smallest program and skips with what failed, so this is a
# gap in coverage and not a failure.
#
# lldb. The debug-information cases want lldb on PATH and say so when it is
# absent. The DWARF is still emitted and still checked for
# self-consistency; what is skipped is the half that says a person can
# actually sit in a debugger.
#
# @sanitize and @valgrind. Out of @checks by design — tens of minutes each
# — and out of here for the same reason. They are the thing to run before
# a release, by hand, not the thing to run on every push.
#
# One platform. ubuntu-latest only. macOS and Windows portability is
# recorded as real and not current (aligned_alloc, MSG_NOSIGNAL,
# stdatomic), and a matrix that goes red for known reasons teaches people
# to ignore red.
name: checks
on:
push:
pull_request:
# A superseded run is wasted minutes and a confusing status on the commit it
# no longer describes.
concurrency:
group: checks-${{ github.ref }}
cancel-in-progress: true
jobs:
checks:
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- uses: actions/checkout@v4
# clang compiles the IR and the runtime's C; llc and ld are the live
# loop, which never calls the clang driver; as is the --x86 path; emacs
# is what test_emacs and test_cider drive the client with, and they skip
# silently without it, which would be four suites' worth of coverage
# disappearing without a word.
- name: Install the toolchain
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
clang llvm lld binutils emacs-nox
# llc has to come from the same LLVM release as clang. The live loop
# goes llc + ld -shared + dlopen rather than through the clang driver,
# so a mismatch breaks `C-c C-c` and every reload test while `flan
# build` keeps working perfectly — which is a confusing shape of failure
# to debug from a CI log. Ubuntu installs llc under a version suffix and
# may or may not provide the bare name, so the version is read off clang
# and FLAN_LLC is pinned to match it rather than left to PATH order.
- name: Point FLAN_LLC at the llc that matches clang
run: |
set -eu
clang --version
major=$(clang --version | sed -n 's/.*version \([0-9][0-9]*\).*/\1/p' | head -1)
if command -v "llc-$major" > /dev/null; then
llc="llc-$major"
elif command -v llc > /dev/null \
&& llc --version | grep -q "LLVM version $major"; then
llc=llc
else
echo "no llc at LLVM $major; clang is $(clang --version | head -1)" >&2
echo "what is on PATH: $(ls /usr/bin/llc* 2>/dev/null || echo none)" >&2
exit 1
fi
echo "FLAN_LLC=$llc" >> "$GITHUB_ENV"
"$llc" --version | head -3
- uses: ocaml/setup-ocaml@v3
with:
ocaml-compiler: "5.2"
# There is no (package ...) stanza yet — the install story is Tier 2
# of docs/REVIEW-production-readiness.md and undecided — so there is
# nothing to pin and nothing to resolve dependencies from. The
# library depends on unix and on nothing else.
opam-pin: false
dune-cache: true
- run: opam install -y dune
- name: Build
run: opam exec -- dune build
# --force because the point of a CI run is to run the tests, not to
# discover that dune's cache already knew the answer.
#
# The log is kept and then read, because a suite that passes while
# leaving an unhandled exception on stderr is a suite telling you
# something and being ignored. That happened here — three suites left a
# message-less `Fatal error: exception Flan.Loc.Error(_)` while
# reporting zero failures — and it was found by a person reading a log,
# which is exactly the kind of finding that should not need a person.
- name: The suite
run: |
set -eu
set -o pipefail
opam exec -- dune test --force 2>&1 | tee suite.log
if grep -n 'Fatal error' suite.log; then
echo "the suite passed but left an unhandled exception above" >&2
exit 1
fi
# The part `dune test` does not cover: the reference page's examples
# still print what the page says, the hand-written x86 backend still
# agrees with LLVM over the whole corpus, and a --dev build still calls
# through its indirection cells.
- name: Everything else that can fail
run: opam exec -- dune build @checks