← board

Toolchain CLI / user tooling (install, config, discovery, doctor, selfcheck)

Shape: installer is a script; everything else is a pxx flag

Config resolution (the foundation — get this right first)

The compiler already anchors lib/rtl, lib/pcl, builtin/ to ExeDir (<root>/compiler/ -> ../lib/...). Generalize to an explicit, inspectable order:

  1. CLI flags (-Fu, -I) — highest.
  2. Env (PXX_HOME, optional PXX_LIBPATH) — for non-default installs.
  3. A config file (pxx.cfg, FPC-fpc.cfg-analog) next to the binary or in ~/.config/pxx/ — optional.
  4. ExeDir-relative defaults — the zero-config install path (works today).

Lower tiers are fallbacks; nothing required for a normal unpack-and-run install.

pxx flags to add

Native-target detect + alias

uname -m -> the matching bin/pxx-<arch> -> symlink/alias pxx. Lives in setup.sh (pre-PATH); pxx --where confirms which binary is active afterward. (Overlaps feature-release-packaging setup.sh — implement once there, surface the result here via --where.)

Non-goals

Sequencing

  1. Config resolution (tiers above) + pxx --where — foundation, makes every "can't find units" issue self-diagnosable. Highest value, do first.
  2. pxx --list-targets / --version — trivial, ride on existing data.
  3. pxx --list-libraries + pxx --doctor — discoverability/diagnostics; grow the library registry as IDF/Synapse/etc. mature.
  4. pxx --selfcheck — with feature-release-packaging (needs the manifest).

Landed 2026-08-21 — steps 1 (partly) and 2

Everything below is a flag on the binary, answering with no source file and exiting 0, and covered by rows in test-quick (so gate.sh quick sees them).

Checked, not assumed: a binary copied outside the tree compiles uses sysutils under PXX_HOME and fails without it; the stable-layout probe re-anchor is visible in --where from a fake two-levels-down install; and the same source compiled with and without PXX_HOME emits identical bytes — the env tier is a second door onto bug-a-the-compilers-output-depends-on-argv0, and it is shut.

Landed 2026-08-21 — step 3: --list-libraries and --doctor

Both answer with no source file and exit 0, like the other four.

--list-libraries SCANS, it does not recite. The unit lists come from a new PxxListDir over exactly the directories ResolveToolchainDirs resolves, so a unit appears the day it lands and a hardcoded inventory cannot go stale. The cost, stated rather than hidden: it says what you can uses, not what each unit DOES — a one-line purpose per unit kept in compiler.pas would drift for precisely the reason the scan exists. External integrations (ESP-IDF, Synapse) are a separate, curated section, because they are not units in this tree and their answer is a toolchain prerequisite rather than a filename.

A directory that does not resolve is REPORTED as [MISSING], never omitted: an empty section reads as "this library does not exist", which is the wrong diagnosis for what is nearly always a path problem.

--doctor reports CAPABILITY, not inventory. Every row is something you might try to do — run an aarch64 binary, flash an ESP32, cold-bootstrap from FPC, step in gdb — and a NO row says what to install, because qemu-aarch64: not found three commands later is the same information delivered at the worst moment. Nothing in it is fatal and the last line says so: pxx compiles and runs native programs with every row missing.

Probing without execve (the self-hosted compiler has none): WhichOnPath walks $PATH and sysopens each candidate — a tool nobody may read is not one this compiler could hand work to either.

One directory scanner, not two

--list-libraries needed a directory listing, and one already existed inside ResolveCaseInsensitivePath (elfwriter.inc) as an inline getdents64 walk with an FPC FindFirst twin. Copying it would have been the smaller diff and the wrong move — devdocs/dev/normalise-dont-special-case.md is about exactly this, and the copy nobody runs is the one that rots (this session already found five such files in the sweep). So the walk became PxxListDir, and ResolveCaseInsensitivePath is now a CaseEqual loop over its result.

Safe to share because the fallback is not a hot path: it runs only after LoadFile returned empty. Verified both directions afterwards — uses myunit resolving MyUNIT.pas through the fallback, and the exact-case file still resolving without it.

DirEntTruncated is not decoration. A scan that silently stopped at the cap would make a MISSING unit look like a case mismatch, so the fallback refuses to answer when it is set.

Still open (why this returns to backlog, not done)


2026-09-02 (frankH) — step 4 is unblocked, and it is a fork rather than an implementation

feature-release-packaging is in done/, so the stated wait is over. Measured on 8616ed370fa7: --version, --where, --list-targets, --list-libraries and --doctor all answer with no source file and exit 0. --selfcheck is unknown option. It is the only item left.

It is not a small implementation. The spec in feature-release-packaging defines check 1 as pxx -> gen1, then gen1 -> gen2, cmp gen1 gen2 — a real fixedpoint STEP, which means running the freshly built binary. The compiler spawns no process (no PalVforkAndExec / PalFork anywhere under compiler/) and locates itself only through ExeDir. Every in-process alternative asserts something weaker or noisier under the same trusted name, and tools/selfcheck.sh already does the specified thing and already ships inside the release tree.

Filed as [[decide-what-should-pxx-selfcheck-assert-when-the-compiler-cannot-spawn]] with four options and a recommendation. If that decision is "use the script", this ticket closes on the answer alone — the other five flags are done.

2026-09-05 (frankH) — re-measured, and the frontmatter was the defect

Nothing has moved on the work: --selfcheck still answers unknown option at HEAD, and the decision it waits on is still status: new, owner: user, filed 2026-09-02.

What HAD moved is that this ticket said so only in prose. Its frontmatter was two lines — prio and track — with no blocked-by, no status, and no summary at all, so ready --track T offered it as unblocked work and the only part everyone reads did not exist. The next taker would have re-derived the same fork: five flags done, one blocked on intent, discoverable in about twenty minutes of reading.

Frontmatter modernised and the blocking edge recorded where the tooling can see it, per the convention progress.sh check prints: prose stating a blocking relationship must also carry the frontmatter edge.