flan/.github/workflows/checks.yml
Joseph Ferano 57fe91f303 Five records become one, and every citation lands somewhere
FIX.org, NEXT.md, DISCUSS.org, docs/DISCUSS.md and the session handoff at the
root are one TODO.org now: 293 entries under seven subsystem headings, each
carrying an org keyword that says where it stands. A DONE entry is a few lines
saying what was decided and what that rules out; the reasoning that would not
compress — the embedding spike and the four reports the hand-written x86
backend was built from — moved into docs/BUILT.md instead, and its entries
point there in one line.

Every entry was checked against the tree before it got a keyword, and the
prose was wrong in both directions. Things the deleted files called open were
built: the first-evaluation stall, main being redefinable, macro parameter
lists, the type-limit constants, the array constructors, the byte fills,
inc/dec, the discard's fontification, the Emacs buffers, rt_die's _exit, the
backtrace surface, and the acceptance failure that could print and still exit
zero. Things they called done were not: the backend reports' no-plan buckets
had gone stale in the other direction, the value-dependent defvar was
superseded rather than built, and macro-expansion source locations are on an
unmerged lane, so that entry is NEXT and names the branch.

Every comment that cited one of the five by name now cites a heading that
exists, in TODO.org or in docs/BUILT.md. The session reports under
docs/handoffs/ keep naming the files they worked on, because rewriting them
would falsify what those sessions did; each carries a note saying where the
content went.
2026-09-21 21:05:48 +07:00

150 lines
6.7 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 TODO.org has
# deprioritised under "The web target does not reach four things", 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