Class flixw


public final class flixw extends Object
Stage 0 of the flixw bootstrap: one file, no dependencies, Java 21.

It owns project discovery, lock parsing, drift detection, version validation, Java selection, compiler acquisition, unconditional digest verification, compiler-first verb dispatch, the wrapper's own verbs, and the process launch. The two shims that reach it, flixw and flixw.cmd, own exactly one decision each -- which java -- plus one cache lookup, because logic in a shim has to be written twice and cannot be unit-tested.

The stock Flix compiler is never modified, patched, or linked against. It is fetched by URL, verified against a SHA-256 committed in .flixw/lock.toml, and executed as an opaque process. The digest is recomputed on every invocation: there is no install stamp and no flag that skips it.

These docs are published from the flixw repository and cover every member, private ones included, because the internals are what a reader has to trust before letting this file download and run a compiler. docs/CONTRACT.md is the description of what ships and what is promised; this is how it is done.

See Also:
  • Field Details

    • WRAPPER_VERSION

      static final String WRAPPER_VERSION
      See Also:
    • WRAPPER_DIR

      static final String WRAPPER_DIR
      See Also:
    • MIN_JAVA

      static final int MIN_JAVA
      See Also:
    • SOURCE_FLOOR

      static final int SOURCE_FLOOR
      The oldest javac that can compile this file, which is a different number from the floor above and answers a different question. MIN_JAVA is what the *compiler* needs; this is what *stage 0* needs, and between the two lies the range where flixw runs, says the pinned Flix will not, and can fetch a JDK that will. Below it flixw cannot speak at all -- which is why the no-java diagnostic does not offer to install one. `tests/lint.sh` compiles this file with --release SOURCE_FLOOR so the number cannot quietly drift when a newer language feature is used.
      See Also:
    • TESTED_CEILING

      static final int TESTED_CEILING
      The interval flixw is tested on. Above the ceiling is a warning, not an error. The number means the suite has actually been run there, so it moves when that is done and not when a JDK is released: `.github/workflows/ci.yaml` runs the whole suite on the ceiling as well as on MIN_JAVA, which is what keeps the claim true rather than aspirational.
      See Also:
    • PROBE_TIMEOUT

      static final Duration PROBE_TIMEOUT
      Bounds for the two child processes stage 0 runs for information rather than for work. Both are generous: exceeding one means the child is wedged, not slow.
    • HELP_TIMEOUT

      static final Duration HELP_TIMEOUT
    • HELP_CAP

      static final int HELP_CAP
      See Also:
    • WRAPPER_VERBS

      static final List<String> WRAPPER_VERBS
    • BUILTIN_VERBS

      static final List<String> BUILTIN_VERBS
      Fallback verbs from Flix 0.77.0. Used when `flix --help` cannot be captured or parsed. Its only job is to answer "does the pinned compiler already implement one of WRAPPER_VERBS" -- a question whose answer changes at most once a year, and never silently. Being one release stale here costs nothing; failing here would brick every project pinned to a compiler flixw has not seen.
    • T0

      static long T0
    • SEMVERISH

      static final Pattern SEMVERISH
    • LOCK_SCHEMA_VERSION

      static final String LOCK_SCHEMA_VERSION
      The lock format's major version, which is not the wrapper's. It changes only when a lock this stage 0 writes would stop being readable under the rules below; adding an optional key is not such a change, and does not move it.
      See Also:
    • PAGES_BASE

      static final String PAGES_BASE
      Where the generated documentation and the JSON Schema are published.
      See Also:
    • LOCK_SCHEMA_URL

      static final String LOCK_SCHEMA_URL
      The URL written into every generated lock as a `#:schema` directive, and the `$id` of the schema itself. Taplo and Even Better TOML read that directive, so an editor validates the lock with no per-project configuration.
      See Also:
    • REPO_PATTERN

      static final String REPO_PATTERN
      GitHub's own limits on the two path segments; a fork may live anywhere within them.
      See Also:
    • JAVA_PIN_PATTERN

      static final String JAVA_PIN_PATTERN
      A feature release or an exact one, and nothing else -- no ranges, no vendor.
      See Also:
    • LOCK_SCHEMA

      static final List<flixw.LockField> LOCK_SCHEMA
      Every key a lock may hold, in the order a generated lock writes them. required means required when the table it sits in is present, which is why `[java] version` is optional: a project that does not care which JDK runs the compiler omits the table entirely, and an empty one means the same thing.
    • NOTED_LOCKS

      static final Set<String> NOTED_LOCKS
      Locks already reported on, so a second read in the same run stays quiet.
    • UPSTREAM_REPO

      static final String UPSTREAM_REPO
      Where the stock compiler comes from when nothing says otherwise.
      See Also:
    • PIN_USAGE

      static final String PIN_USAGE
      One usage line for `pin`, because four diagnostics quote it and the fourth was already a release behind the first the last time one was written out by hand.
      See Also:
    • INFO_USAGE

      static final String INFO_USAGE
      See Also:
    • DOCTOR_USAGE

      static final String DOCTOR_USAGE
      See Also:
    • VALIDATE_USAGE

      static final String VALIDATE_USAGE
      See Also:
    • EXAMPLES_USAGE

      static final String EXAMPLES_USAGE
      See Also:
    • LOCAL_USAGE

      static final String LOCAL_USAGE
      See Also:
    • EXAMPLES_LOCAL_USAGE

      static final String EXAMPLES_LOCAL_USAGE
      See Also:
    • PLUGIN_NAME_PATTERN

      static final String PLUGIN_NAME_PATTERN
      A single path segment, nothing else -- in particular no `.`, so a name can never climb out of <cache>/plugins/ the way .. would. Checked at every point a name reaches a path: the three CLI entry points, and a lock's own [plugins.<name>] table, which is attacker-controlled the moment a lock is.
      See Also:
    • METADATA_CAP

      static final int METADATA_CAP
      Adoptium answers in a few tens of KiB; this is room to spare, not a target.
      See Also:
    • UNSAFE

      static final Pattern UNSAFE
    • VERSION_TOKEN

      static final Pattern VERSION_TOKEN
      A version token standing on its own, rather than one buried inside a longer word. Built from SEMVERISH so the two cannot drift into disagreeing about what a version is.
    • HEADER_LINES

      static final int HEADER_LINES
      How much of a help screen counts as the header for parseReportedVersion(java.lang.String).
      See Also:
    • COMMAND_ENTRY

      static final Pattern COMMAND_ENTRY
      Two-space indent, a lowercase name, then the column gap before its description.
    • LOCAL_BOOKKEEPING_VERBS

      static final Set<String> LOCAL_BOOKKEEPING_VERBS
      Bookkeeping verbs work before a compiler is pinned, like plugin install.
    • GH_ASSET

      static final Pattern GH_ASSET
      A GitHub release asset URL, split so only the tag has to change to find a newer one.
    • PLUGIN_USAGE

      static final String PLUGIN_USAGE
      See Also:
    • SHIPPED

      static final List<String> SHIPPED
      Every path the installer writes and the project commits — the set the block pins and the set git has to carry. One list, because it was two: .sccignore shipped into the block while both copies still named five files, so a later rule re-pointing its endings went unreported and nothing noticed it was untracked.
    • MILL_COMPILER_JAR

      static final String MILL_COMPILER_JAR
      The one output produced by Flix's supported Mill assembly task.
      See Also:
    • RELEASES_API

      static final String RELEASES_API
      Where latestPrereleaseTag() asks, since there is no /latest for it.
      See Also:
    • COMPLETION_SHELLS

      static final List<String> COMPLETION_SHELLS
    • COMPLETION_USAGE

      static final String COMPLETION_USAGE
    • JDK_ASSET

      static final String JDK_ASSET
      The optional JDK provisioner; see runJdkAsset(int).
      See Also:
    • SETUP_ASSET

      static final String SETUP_ASSET
      See Also:
    • INSPECT_ASSET

      static final String INSPECT_ASSET
      The cache inventory behind info --verbose; see listCache(flixw.Lock, flixw.Jvm).
      See Also:
    • CLI_ASSET

      static final String CLI_ASSET
      See Also:
    • EXAMPLES_ASSET

      static final String EXAMPLES_ASSET
      See Also:
    • LOCAL_ASSET

      static final String LOCAL_ASSET
      Overrides a declared GitHub dependency with a local checkout; see the "local" case in wrapperVerb(java.lang.String, java.util.List<java.lang.String>, java.nio.file.Path, flixw.Lock, java.nio.file.Path, flixw.Jvm, java.util.List<java.lang.String>, java.lang.String) and the examples local routing in the "examples" case.
      See Also:
    • PICOCLI_VERSION

      static final String PICOCLI_VERSION
      picocli, published as a flixw release asset like every other companion.

      It is the one third-party dependency in flixw, used only to render companion-asset help and generate completion. Stage 0 does not link against it, does not parse arguments with it and never loads it: flixw's own argument handling stays auditable without reading anyone else's code, which is the property this project exists to have.

      Republished rather than fetched from Maven Central. A second download origin would be a second trust story -- outside the release's own SHA256SUMS, outside FLIXW_ASSET_SOURCE, unwarmed by wrapper --upgrade and unreachable from a mirror -- for a wrapper whose entire argument is that everything it runs came from one place and was checked against one manifest. Going through ensureAsset(java.lang.String) means the renderer's dependency is verified, mirrored, warmed and purged exactly like stage 0 itself, and costs no new code to do it. picocli is Apache-2.0, so redistribution is a licensing non-event.

      See Also:
    • PICOCLI_ASSET

      static final String PICOCLI_ASSET
      See Also:
    • SPECS_ASSET

      static final String SPECS_ASSET
      Curated Flix command specs for the renderer's class path: flixw's data, so a flixw release rather than picocli's. Optional -- without it the renderer reads the compiler's own --help, as for any version nobody curated.
      See Also:
    • SHIM_SHA256

      static final String SHIM_SHA256
      See Also:
    • CMD_SHA256

      static final String CMD_SHA256
      See Also:
  • Constructor Details

    • flixw

      public flixw()
  • Method Details

    • fail

      static flixw.Fail fail(String code, int exit, String msg)
    • w001

      static flixw.Fail w001(String m)
    • w002

      static flixw.Fail w002(String m)
    • w003

      static flixw.Fail w003(String m)
    • w004

      static flixw.Fail w004(String m)
    • w005

      static flixw.Fail w005(String m)
    • w006

      static flixw.Fail w006(String m)
    • w007

      static flixw.Fail w007(String m)
    • w008

      static flixw.Fail w008(String m)
    • w009

      static flixw.Fail w009(String m)
    • w010

      static void w010(String m)
      FLIXW010 and FLIXW011 are advisory: they are printed, they never set exit status.
    • w011

      static void w011(String m)
    • env

      static String env(String k)
    • trace

      static boolean trace()
    • tr

      static void tr(String s)
    • validateVersion

      static String validateVersion(String v, String where)
    • stripTagPrefix

      static String stripTagPrefix(String v)
      Accepts the release tag where a version is expected: v0.75.2 means 0.75.2. GitHub shows the tag, not the version. The releases page, the tag list, the archive links and the asset URLs all read v0.75.2, so copying from where the versions actually are gets you the tag every time -- and flixw itself builds "v" + version to construct that URL, so it already holds that the two name one release. Refusing the form flixw prints into its own URLs made the user do a normalization the wrapper was doing anyway. Only ahead of a digit, so vNext is still a bad version rather than the version Next, and the diagnostic keeps naming the real problem. Deliberately not applied to [package].flix: that field is Flix's, and Flix accepts x.x.x alone. Tolerating a tag there would let flixw read a manifest that Flix itself rejects, which is a worse outcome than the error it replaces.
    • canonical

      static String canonical(String v)
      The single normalization used for release tags, cache coordinates, and every version comparison. SemVer build metadata identifies a build, not a release, so it is accepted in the manifest and stripped everywhere it would name an artifact. Defining this once is what stops `flix = "0.75.2+build.4"` from producing a drift error that `./flixw pin` cannot repair.
    • triple

      static String triple(String v)
      The `x.x.x` that `[package].flix` is allowed to hold. That field is Flix's, not flixw's, and Flix rejects anything else outright -- "This toml file has a Flix version number of the wrong length" for a version carrying build metadata. It also accepts 99.99.99 against a 0.75.2 compiler, so it reads as a coarse floor rather than a pin. The exact version therefore lives in the lock, which is flixw's own file and can say `0.75.2+fork.wstein.260807.1` without breaking anything. Drift compares the two at this precision, because that is all the manifest is able to express.
    • q

      static String q(String s)
    • why

      static String why(Exception e)
      IOException.getMessage() is often bare the path; name the failure too.
    • redact

      static String redact(String v)
      Redacts credentials from a URL-shaped value before it is printed. `doctor` output exists to be pasted into bug reports, and a proxy URL is the one environment value that routinely carries a password. Host and port are what a reader needs; user-info and query string never are. Values that are not URLs at all -- a NO_PROXY host list, say -- have no '@' and pass through untouched.
    • redactOpts

      static String redactOpts(String v)
      The same, for JVM option strings, which can carry -Dhttps.proxyPassword=secret.
    • lockTables

      static List<String> lockTables()
      The tables the schema knows about, deduplicated, in lock order. The root is "".
    • lockSchemaJson

      static String lockSchemaJson()
      The published JSON Schema for lock.toml, rendered from LOCK_SCHEMA. Generated rather than hand-written for the reason the shims are compared byte for byte: a schema describing a lock this wrapper no longer writes is worse than no schema at all, because an editor presents it as authority. `tests/lint.sh` diffs this against the copy in `docs/schema/`, so the published file cannot drift from the code that writes the file it describes. Hand-rolled rather than serialised by a library, because stage 0 has no dependencies. The only values interpolated are ours, and jsonString(java.lang.String) escapes them anyway -- the patterns are full of backslashes.
    • lockFields

      static List<flixw.LockField> lockFields(String table)
      The fields declared for one table, in lock order.
    • fieldJson

      static String fieldJson(flixw.LockField f, String indent)
    • tableJson

      static String tableJson(String table, String indent)
    • pluginsTableJson

      static String pluginsTableJson(String indent)
      [plugins.<name>] for every name at once: an object whose keys are arbitrary (plugin names) but whose values all share one shape, which JSON Schema expresses with additionalProperties as a sub-schema rather than properties.
    • jsonArray

      static String jsonArray(List<String> items)
    • jsonString

      static String jsonString(String s)
      JSON string literal. Only the escapes RFC 8259 requires; every value here is ASCII.
    • tomlScan

      static flixw.TomlScan tomlScan(String text, String where)
      The single TOML line scanner in stage 0. This is not a TOML parser and does not try to be one -- stage 0 has no dependencies by design. It is deliberately table-aware, comment-aware and multi-line-string-aware, because the alternative that a plain regex gives you is reading `flix = "..."` out of some unrelated table, or out of the body of a description string. There is exactly one of these because there used to be two: `pin`'s rewrite carried a second copy that had never learned about multi-line strings, so a `flix = "9.9.9"` inside a `"""` description was correctly invisible to the lookup and yet rewritable by pin. Any divergence here means the version flixw reads is not the one it writes, so the two readers share a scanner rather than a convention. Lines are split on \n alone, never on \r?\n: `pin` rejoins with \n to rewrite a single line in place, and a split that swallowed the \r would quietly convert a CRLF manifest to LF. The trailing \r survives into the raw line and is removed by trim().
    • isKey

      static boolean isKey(flixw.TomlEntry e, String table, String key)
      True when an entry is `table.key`. Dotted keys are resolved to their table by tomlScan(java.lang.String, java.lang.String), so both spellings arrive here already in the same shape.
    • splitKey

      static List<String> splitKey(String raw, String where)
      Splits a key into its segments, respecting quotes, then unquotes and trims each one. `a.b` is two segments; `"a.b"` is one. Fails closed: an unterminated quote or an empty segment is a manifest this scanner will not guess at.
    • tomlLookup

      static String tomlLookup(String text, String table, String key, String where)
      Reads one key from one TOML table. Anything it cannot classify inside the table it was asked about is rejected rather than guessed at. Duplicate tables and duplicate keys are ambiguous, so they fail rather than resolve. Accepts the key inside [table] and as a dotted key at the root (`package.flix`).
    • bracketDelta

      static int bracketDelta(String line)
      How much this line opens or closes an inline array, counting only brackets outside quotes. Used to skip a value that spans lines; it never goes below zero, because a stray closing bracket is not this scanner's business to diagnose.
    • stripComment

      static String stripComment(String line)
      Strips a trailing comment, ignoring '#' inside quotes.
    • unquote

      static String unquote(String s)
    • unquoteToml

      static String unquoteToml(String v, String where)
      A quoted TOML value, fully unescaped -- unlike unquote(java.lang.String), which every existing caller uses for a version, URL or digest, none of which can legally contain a backslash, so stripping the outer quotes has always been the whole job. A task's command string is arbitrary shell syntax, where `\"` and embedded quotes are the ordinary case, so this processes TOML's basic-string escapes for real. A single-quoted (literal) string has none to process by definition -- exactly the TOML feature that lets a task avoid this entirely by not using `"..."`.
    • lockPath

      static Path lockPath(Path root)
    • tasksPath

      static Path tasksPath(Path root)
    • readTasks

      static Map<String,String> readTasks(Path root)
      `.flixw/tasks.toml`: npm-`scripts`-style name-to-shell-string pairs, hand-edited and committed like `lock.toml` itself, but never generated or rewritten by `pin` or `doctor --fix` -- unlike the lock, this file is the human's to write, so it carries no `#:schema` directive and no "generated" header. Flat by design: a table would invite grouping that a shell string running through `sh -c`/`cmd /c` gets no benefit from, and it is one fewer thing tomlScan(java.lang.String, java.lang.String)'s callers here have to check for.
    • readLock

      static flixw.Lock readLock(Path lockFile)
    • readPlugins

      static Map<String,flixw.PluginDep> readPlugins(String text, String where)
      [plugins.<name>] tables, keyed by name -- a dynamic set `LOCK_SCHEMA`'s fixed-table-and-key model cannot describe, so it is read directly from tomlScan(java.lang.String, java.lang.String) rather than through readLockFields(java.lang.String, java.lang.String). Each declared plugin needs `version` and `sha256`; `source` is optional and never used to fetch anything, only shown to a reader deciding what to install.
    • readLockFields

      static Map<String,String> readLockFields(String text, String where)
      Reads every key LOCK_SCHEMA declares, keyed as `table.key` with the root table's keys unprefixed. Absent optional keys are simply not in the map. Presence and shape are both checked here, from the same list the published JSON Schema is rendered from, so a lock an editor flags is a lock flixw refuses -- and the diagnostic can say what the key is *for* rather than quoting a regex at someone.
    • noteUnknownLockKeys

      static void noteUnknownLockKeys(String text, String where, String wroteIt)
      Keys the schema does not describe, reported once and never fatally. Advisory because the ordinary way to meet one is a lock written by a newer flixw, and refusing to run would make such a project unbuildable by every collaborator who had not upgraded yet -- the lock is committed, so that is most of them. Silence is the wrong answer too: a mistyped key is otherwise invisible, and the value someone believed they had set is simply never read. A lock that says it was written by a newer flixw gets no note at all, because there the unknown key is expected and the message would be wrong as well as noisy.
    • unknownLockKeys

      static List<String> unknownLockKeys(String text, String where)
      Every key in the file that LOCK_SCHEMA does not describe, named the way a diagnostic names it, in file order and without repeats. Separate from the note because `doctor --fix` asks the same question for the opposite reason: it regenerates the lock from the values it read, which would *delete* any key it did not read.
    • manifestVersion

      static String manifestVersion(Path manifest)
      The manifest is the human authority; disagreement stops us before the network. A manifest that exists but cannot be read is an error, not an absent declaration -- swallowing it would silently disable drift detection and let the compiler run.
    • isWindows

      static boolean isWindows()
    • isMac

      static boolean isMac()
    • cacheHome

      static Path cacheHome()
    • sha256

      static String sha256(Path file)
    • sha256

      static String sha256(byte[] b)
    • isUpstream

      static boolean isUpstream(flixw.Lock lock, boolean override)
      Whether a fact verified against upstream Flix's own behaviour -- not its rendered --help layout, which any fork can reproduce trivially -- is safe to apply here. FLIX_JAR is excluded unconditionally: an override is announced as unverified and is explicitly not stock-compatibility evidence, the same reason reportOverrideGap(flixw.Lock, java.nio.file.Path) exists.
    • localHelpArgs

      static boolean localHelpArgs(List<String> rest, Path root, flixw.Lock lock, Path jar, flixw.Jvm jvm, List<String> compilerVerbs, String verbId)
    • localHelpSubcommand

      static boolean localHelpSubcommand(String sub)
    • wantsHelp

      static boolean wantsHelp(List<String> rest)
      --help/-h anywhere in a wrapper verb's own arguments, the same way a user expects it to work on any CLI. pin, info and doctor otherwise treat an unrecognised --xxx as a usage error, so without this check --help was indistinguishable from a typo. pin is checked at its own call sites in realMain(java.util.List<java.lang.String>) rather than in wrapperVerb(java.lang.String, java.util.List<java.lang.String>, java.nio.file.Path, flixw.Lock, java.nio.file.Path, flixw.Jvm, java.util.List<java.lang.String>, java.lang.String) -- it bypasses that dispatcher entirely, being the one verb answered before a compiler is ever reachable. Checked before the verb's own grammar runs, so it can never be shadowed by an "unknown option" diagnostic firing first -- which is exactly what happened before this existed.

      Only scans up to the first bare --, not the whole list. No wrapper verb that calls this has a reason for a literal -- to appear in its own arguments, so the guard is free insurance for all of them -- the one verb that genuinely forwards past a -- is examples, and it does not call this method at all: once a real verb (run/check/build/test) is named, --help/-h is never intercepted in any position, because the compiler itself answers it better than a generic usage line once a verb is in play. See the "examples" case below.

    • validPluginName

      static boolean validPluginName(String name)
    • checkRepo

      static String checkRepo(String repo, String where)
    • encodeTag

      static String encodeTag(String tag)
      A release tag in a URL path. Only '+' needs it; the rest of a version is path-safe.
    • forkAssets

      static List<String> forkAssets(String repo, String version)
      A fork's two asset conventions, flix-<version>.jar and flix.jar.
    • resolveRelease

      static String resolveRelease(String repo, String version)
    • httpClient

      static HttpClient httpClient()
      The one HTTP client, pinned to HTTP/1.1. Every request flixw makes is a single one-shot HEAD or GET, so HTTP/2 buys nothing here -- there are no concurrent streams to multiplex onto one connection -- and it costs a failure mode that only shows up as a red CI run. When a server sends GOAWAY while a stream is being opened, the JDK client raises `request not processed by peer`; because acquisition is one attempt with no retry loop, that lands on the user as a failed download and, through the missing lock, as fifteen further failures. That is a real observation, not a theoretical one: it took out the whole windows smoke job on ccba32b while ubuntu and macos passed the same commit. Pinning 1.1 deletes the race rather than retrying around it, which is the trade this project already makes everywhere else -- a retry would have to be bounded, logged and explained, and would still leave the request that *was* processed ambiguous.
    • assetExists

      static boolean assetExists(String url)
      Does this release asset exist? A HEAD, so the download itself stays a single attempt.
    • parsePin

      static flixw.Pin parsePin(List<String> args, flixw.Lock existing)
      ./flixw pin [<owner>/<repo>] [<version>] [--java <version>], or ./flixw pin --refresh. The two are told apart by the slash, which a version can never contain -- the grammar rejects it -- so the order does not matter and neither does a flag. An omitted repository means the one already in the lock, so re-pinning a project that tracks a fork stays on that fork: rebuilding the upstream URL every time silently moved such a project back to stock, and because both are honestly version 0.75.2, nothing about it looked wrong.
    • insideCompilerCache

      static boolean insideCompilerCache(Path jar)
      True when a path names something inside the cache flixw fills with pinned compilers.
    • reportOverrideGap

      static void reportOverrideGap(flixw.Lock lock, Path jar)
      Says so when FLIX_JAR names a compiler out of flixw's own cache. A mismatch between the override and the lock is the *ordinary* case -- the override exists to run a jar you built yourself, which is not the pinned one and is not meant to be -- so it is reported where state is printed, and not on every run. Pointing it inside <cache>/compilers/ is different, and is always a mistake. Those names are content-addressed, flix-<version>-<sha256>.jar, so **the path changes every time the project is re-pinned**. An override set once to whatever `info` reported that day goes on naming the superseded artifact afterwards, and the project quietly builds with the compiler it used to pin. Nothing else in flixw could catch it: the digest guard is switched off by the override, and the version check passes because two builds of one release share a canonical version. Matching the lock is a mistake too, only a harmless one: it names the jar flixw would have chosen anyway, and it will stop doing that at the next pin.
    • compilerPath

      static Path compilerPath(flixw.Lock lock)
    • markUsed

      static void markUsed(String key)
      A flixw-owned last-use record, deliberately independent of filesystem atime.

      One immutable cache artifact has one opaque marker containing only its UTC date. Reading first avoids rewriting it on every compiler launch, which matters both for SSD wear and for making a purge record mean a day of actual use rather than a day a wrapper happened to start. Failure is non-fatal: cache lifecycle must never stop a build, and an entry with no usable record is retained by purge.

    • validateDistUrl

      static void validateDistUrl()
      Validated on every run, not only when a download happens: a warm cache would otherwise hide a malformed mirror setting until the day it is actually needed.
    • validateUrl

      static void validateUrl(String url, String where)
      Structural validation, so a malformed lock produces a FLIXW diagnostic rather than an uncaught IllegalArgumentException from URI.create deep in the download path.
    • rewriteBase

      static String rewriteBase(String url)
    • acquire

      static Path acquire(flixw.Lock lock)
    • downloadAdvice

      static String downloadAdvice(int status)
      A 5xx is the host failing; sending that user to re-read their pin is a false lead.
    • download

      static void download(String url, Path dest)
    • runCapture

      static String runCapture(List<String> cmd, Duration timeout, int cap) throws IOException
      Runs a child and returns its merged output, bounded in both bytes and wall clock. Returns null when the child did not finish in time, or its output could not be read. The obvious shape -- a read loop with a deadline test in its condition -- bounds nothing: the test runs *between* reads, and read() on a pipe blocks until the writer produces a byte or closes it. A child that starts and then answers nothing parks stage 0 inside that one call forever, which is precisely what a process run for information must never do. So the read runs on a daemon thread and the timeout is enforced on the process, which is the only handle that can actually be revoked. The byte cap is a separate bound: a chatty child would otherwise exhaust the heap.
      Throws:
      IOException
    • diagnostic

      static boolean diagnostic(String verb)
      Verbs that must answer even when no java satisfies the lock.

      The same set that stays in stage 0 permanently, minus `pin`, which is dispatched before java selection is reached at all. They are what a fresh clone or a broken checkout has to be able to run: a wrapper whose diagnostics need the thing they diagnose has none.

    • runningJvm

      static flixw.Jvm runningJvm()
      The JVM already executing this code, which is by construction usable.
    • exeIn

      static Path exeIn(String home)
    • probe

      static int probe(Path exe)
      Reads <home>/release when present and parseable; else runs the candidate once.
    • probeVersion

      static String probeVersion(Path exe)
      The candidate's own version string -- `21.0.12` rather than `21` -- so a pin can be as exact as the person who wrote it chose to be. Same two sources as probe(), in the same order and for the same reasons: the release file costs one read, and executing the candidate is the fallback for a java that is not laid out like a JDK.
    • satisfiesJavaPin

      static boolean satisfiesJavaPin(String pin, String version)
      Does this JDK satisfy `[java] version` in the lock? A pin is a prefix of the version, cut at a dot: `21` accepts 21.0.12, `21.0` accepts 21.0.12, and neither accepts 21.1 or 2. Prefix rather than equality because the useful pin is nearly always "this feature release", and the exact one is available to whoever wants it by writing more of the number. Vendor is deliberately not part of it -- the pin says which Java the project needs, not whose.
    • validateJavaPin

      static void validateJavaPin(String v, String where)
      A pin is a dotted number and nothing else: no ranges, no `latest`, no vendor. It must also be a Java the pinned compiler can actually run under, so a pin below MIN_JAVA is refused at the point it is written rather than at every run afterwards.
    • feature

      static Integer feature(String v)
    • strictJava

      static boolean strictJava()
    • acceptable

      static boolean acceptable(int f, String source)
      Below MIN_JAVA is always fatal: the compiler will not run. Above TESTED_CEILING is a warning, because a JDK upgrade must not break a wrapper whose pinned compiler tolerates it. FLIXW_STRICT_JAVA=1 makes the ceiling fatal for reproducible builds.
    • chooseInstall

      static flixw.Jvm chooseInstall(List<flixw.Jvm> candidates, boolean strict)
      Picks among discovered installations: the newest JDK that is still inside the tested interval, and only if none is, the one just above it. Taking the first acceptable candidate in directory order was the earlier rule, and it answers by filename. On a machine carrying 11, 17, 21, 25 and 26 it selected 26 -- outside the tested interval, and warned about on every run -- because the symlink named `java` sorts before `openjdk@21`. Nothing was wrong with the search; the choice was made by `sort`. Above the ceiling is a last resort rather than a preference, so the lowest such candidate wins: it is the one closest to ground that has actually been tested. Returns null when nothing is usable, which is the caller's cue to fail.
    • selectJava

      static flixw.Jvm selectJava(String pin)
    • markJvmUse

      static flixw.Jvm markJvmUse(flixw.Jvm jvm)
      Records a provisioned JDK only after it won selection, never merely because it was probed.
    • knownInstalls

      static List<Path> knownInstalls()
      Directories a JDK is commonly unpacked into. Deliberately only directories: the OS-native inventories are either unusable or misleading here. `java_home -V` is blind to Homebrew, which on macOS is where the JDKs usually are; `update-alternatives --config` is interactive and wants root; `dpkg`, `rpm`, `scoop list` and `choco list` answer with package names rather than paths; and `find /` is an unbounded walk on a tool that runs on every command. A directory that is not there costs one stat.
    • httpGet

      static String httpGet(String url)
      One bounded HTTPS GET returning text. Metadata only; bytes go through download().
    • installedJdk

      static Path installedJdk()
      The java recorded by the last successful install, if it is still there.
    • findJavaUnder

      static Path findJavaUnder(Path root)
      Layout differs per platform -- macOS nests a .jdk bundle -- so look rather than guess. The executable bit is only required where it means something. Adoptium builds its Windows zip on a Unix machine, so entries carry a mode of 0770, and java.util.zip discards it: every file lands 0644. On Windows that is irrelevant, because what makes java.exe runnable there is the extension and the ACL -- but a check for it would rest on platform semantics rather than on anything unpacking guarantees. On POSIX the bit does mean something and tar preserves it, so it is still required.
    • jdkInstructions

      static void jdkInstructions(int want)
      What to type on this OS, pointing at the same vendor flixw would fetch.
    • javaPinAvailable

      static boolean javaPinAvailable(String pin)
      Is there a JDK on this machine that satisfies the pin? Asked by `pin` so that writing one is not silently a decision to break the next command. It never offers, downloads or throws: the answer is used for a note, and a pin for a JDK this machine does not have is legitimate -- CI may have it, and `--install-jdk` can fetch it.
    • noJavaFound

      static flixw.Jvm noJavaFound(int self, String pin)
      Nothing usable was found: say how to fix it, and stop. Never returns -- the Jvm result type only exists so the sole caller can return it.

      This used to prompt and then download a JDK inline, and that was the wrong shape twice over. It put ~200 lines of third-party metadata parsing, archive handling and per-platform policy inside the file that is loaded on every single invocation, to serve the rarest path there is; and it made an automatic network fetch the default answer to a missing dependency, in a wrapper whose entire argument is that it fetches only what a lock named and a digest confirmed. A precise diagnostic naming the repair is the better answer, and it is the one every other missing-dependency case here already gives. Provisioning is still available -- explicitly, as ./flixw wrapper --install-jdk -- and lives in a verified companion asset.

    • installJdkVerb

      static void installJdkVerb(List<String> argv)
      `./flixw wrapper --install-jdk`, so the choice need not wait for a failure.
    • runJdkAsset

      static Path runJdkAsset(int feature)
      Fetches, verifies and runs the JDK provisioner, and returns the java it installed -- the one line the asset prints on stdout.

      Its diagnostics go straight to this process's stderr, which is what the user is already reading: the asset uses flixw's own FLIXWnnn codes, and a caller must not be able to tell that the work happens outside stage 0's own file.

    • jvmOpts

      static List<String> jvmOpts()
    • tokenize

      static List<String> tokenize(String s)
      One documented tokenizer: whitespace separates; '' and "" quote; \ escapes inside "" and bare.
    • wrapperAnchor

      static Path wrapperAnchor()
      Resolves this file's own symlink chain without physicalizing unrelated directories.
    • resolveLinkChain

      static Path resolveLinkChain(Path p) throws IOException
      Throws:
      IOException
    • findRoot

      static Path findRoot(Path anchor)
      Search upward from cwd for flix.toml, bounded above by the wrapper's own project. Invocation from outside that tree is refused rather than searched: an unbounded walk finds the first stray manifest above cwd and silently builds an unrelated project.
    • verbsFile

      static Path verbsFile(Path jar, String identity)
      Verb records live in the wrapper cache keyed by identity, never beside the JAR: a content-addressed compiler directory is legitimately read-only, and a FLIX_JAR override points at a JAR flixw does not own and must not write next to.
    • helpFile

      static Path helpFile(String identity)
      The compiler's --help, verbatim, beside the verb record and keyed the same way.

      Kept because verbs(java.nio.file.Path, java.nio.file.Path, java.lang.String) already ran the subprocess and threw the text away: the verb list is a lossy parse of that text, so help flix would otherwise pay a second compiler launch for bytes stage 0 held in a local variable one line earlier. Keyed by identity rather than by version, so two forks that both call themselves 0.75.3 cannot collide and a FLIX_JAR override gets its own record instead of overwriting a pinned one.

    • helpMetaFile

      static Path helpMetaFile(String identity)
      Provenance for helpFile(java.lang.String): what the compiler called itself, what was stored, and when it was asked.

      It deliberately does not record the lock's version. This record is keyed by the compiler's digest and is therefore shared between projects -- the same bytes can be pinned as 0.75.3 in one repository and as a fork's own tag in another, both correctly -- so there is no single lock version that belongs to it. The .pin record beside it has the same property and says so.

      Nor does it record the help format. Detecting scopt from picocli is a shape test on a few kilobytes, so caching the answer saves nothing and costs the one thing worth avoiding: a second place that decides what a help screen is, free to disagree with the renderer that actually formats it.

    • writeHelpRecord

      static void writeHelpRecord(String identity, String help)
      Written on the one capture stage 0 already performs; a read-only cache stays silent.
    • verbIdentity

      static String verbIdentity(Path jar, flixw.Lock lock, boolean override)
      Pinned compilers are identified by their locked digest; overrides by path+size+mtime.
    • verbs

      static List<String> verbs(Path javaExe, Path jar, String identity)
    • pinRecordFile

      static Path pinRecordFile(String identity)
      Beside the version record and keyed the same way, so a re-pin gets a fresh one.
    • cachedPinRecord

      static flixw.PinRecord cachedPinRecord(String identity)
      What acquire last wrote for this digest, if any -- a file read, never a subprocess or a re-parse of anyone's lock. Every project that ever acquired this exact jar wrote the same repo and version here, since the digest is the same bytes either way; the last writer is as good as any.
    • fetchedFrom

      static List<String> fetchedFrom(String digest)
      Every URL these exact bytes were actually downloaded from and verified against, one per line -- what pin may reuse a cached jar as, and nothing else.

      Not the .pin record: every ordinary run rewrites that from whatever the lock claims, and a lock is committed text. A fork's build shares upstream's cache name when their canonical versions agree, so a lock naming those bytes but claiming flix/flix relabelled them on its next run, and the next offline pin of that version anywhere on the machine reused fork bytes as stock Flix. A line here is only ever added by a download that just produced these bytes from that URL, so a lock can claim what it likes and add nothing; two releases with identical bytes each keep their own line.

    • recordFetch

      static void recordFetch(String digest, String url)
      Adds url to fetchedFrom(java.lang.String); best-effort, like every cache write. An append rather than a rewrite: one short line under O_APPEND cannot interleave.
    • writePinRecord

      static void writePinRecord(String identity, String repo, String version)
      Written by pin itself, the moment it settles on a digest -- not deferred to the next acquire(flixw.Lock), which may never come: a project that pins one build and then immediately pins another runs no other command against the first digest in between, and a record only acquire writes would never see it. acquire re-affirms the same record on every later run regardless, so a cache populated by an older flixw that predates this file entirely still backfills on its very next use rather than staying silent forever.
    • captureHelp

      static String captureHelp(Path javaExe, Path jar)
      The compiler's `--help`, bounded, as text. The two parses read it separately.
    • captureVerbs

      static List<String> captureVerbs(String out, Path jar)
    • parseReportedVersion

      static String parseReportedVersion(String help)
      The version the compiler says it is, read from the header of its own help. Free, because verbs(java.nio.file.Path, java.nio.file.Path, java.lang.String) already runs --help and the version is sitting in the text it throws away. Both renderers put it in the first lines and neither puts it in the same place: scopt writes The Flix Programming Language 0.75.2 on one line, picocli writes the product name and the version on the next. Rather than encode either layout -- a fork may rename the product string, and one did move the version to its own line -- take the first standalone version token in the header. The header, not the whole screen: an option's default or an example further down is text about something else, and reading one as the compiler's identity would produce a mismatch report about nothing.
      Returns:
      the reported version, or null when the header carries none -- which is not an error, only the absence of a second opinion
    • captureReportedVersion

      static String captureReportedVersion(Path jar, String javaPin)
      Asks a JAR what version it says it is, for the lock. Best-effort in every direction: null when no JVM can be selected, when the JAR will not run, or when its header carries no version token.

      Nothing here may throw. This runs inside pin, which is the documented repair for a project that cannot reach a compiler -- a machine with no usable Java must still be able to write a lock, and losing the second opinion is a far smaller loss than losing the ability to pin at all.

    • reportVersionGap

      static void reportVersionGap(String lead, String pinned, String reported)
      Says so when the two version strings recorded about one compiler disagree.

      The digest settles *which bytes* the lock pins, and nothing settles that those bytes are the release it names. A mislabelled release asset -- a fork that tagged v0.75.4 over a 0.75.2 build, an upstream re-upload -- would otherwise be pinned, verified and run without a word.

      The compiler is *asked* once, by pin, and its answer is recorded in the lock beside the digest. Every later run re-hashes those bytes anyway, so a digest that still matches is a version that still matches: a per-run second opinion would re-derive what the digest already proves, at the cost of a subprocess and a cache file. The comparison, unlike the capture, is free once both strings are in the lock, so it stays on every run -- it is what catches a lock edited or merged after pin wrote it, which is exactly the case pin-time checking cannot see.

      Compared through canonical(java.lang.String), because build metadata identifies a build rather than a release: a compiler built from 0.75.3+stable.names.3 reporting 0.75.3 is agreeing, not disagreeing.

      FLIXW010: printed, never fatal. Pinning a mislabelled asset on purpose is legitimate, and the lock records both strings so validate can decide what a build should do about it.

      Parameters:
      lead - how the disagreement is introduced, which differs between the moment of pinning ("the JAR just pinned") and every run afterwards ("the pinned compiler")
    • parseVerbs

      static List<String> parseVerbs(String out)
      The verbs a help screen advertises, by three independent parses. Two help renderers are in play and neither is a contract. Stock Flix is scopt: the verb list is one `Usage: flix [a|b|c]` line, and each verb repeats as `Command: a`. The picocli-based fork wraps that same bracket across several lines and replaces the per-verb lines with one indented `Commands:` block. A parser that handles only the first reports zero candidates on the second, which is what FLIXW010 was saying. Kept separate from the subprocess that produces the text so it can be tested against both renderers' real output without a JAR; `tests/UnitCheck.java` does exactly that.
    • stage0Dir

      static Path stage0Dir(String srcHash)
    • runAsset

      static int runAsset(Path asset, Path classpath, List<String> args)
      Runs a companion asset, in this JVM when it can be and in its own when it cannot.

      Spawning a JVM from a JVM to run a Java program is a smell, and the numbers never justified it: the fork is 29ms of a 402ms launch. The reasons that did hold were about the asset ending in System.exit -- which would take the wrapper down mid-command -- and about what its class path drags in. The first is fixed at the source: every asset returns its exit code now. The second is what the loader below is for.

      There is no fork any more, and no fallback to one. The fallback existed for three cases and none survived: "no javac" is not one, because a JEP 330 source launch compiles too and fails identically without jdk.compiler (measured against a jlinked java.base-only runtime); an unwritable cache is handled by compiling to a temporary directory instead; and an asset older than run cannot be reached, since ensureAsset(java.lang.String) only ever fetches this release's own. A fallback for situations that cannot arise is a second code path nothing exercises.

      Each asset gets its own URLClassLoader, parented to the platform loader rather than the application one. Stage 0's classes are therefore invisible to it and its to stage 0: picocli loaded for help cannot be reached from the wrapper's own code, and an asset cannot accidentally resolve a stage 0 class instead of its own. The loader is closed when the asset returns, so nothing it loaded outlives the command.

      Returns:
      the asset's exit code
    • runAsset

      static int runAsset(Path asset, Path classpath, Path resources, List<String> args)
      resources joins the loader's class path only: data, so never compiled against.
    • assetMainClass

      static String assetMainClass(Path asset)
      flixw-cli.java declares flixwcli; that is the whole convention.
    • compiledAssetDir

      static Path compiledAssetDir(String srcHash)
      <cache>/assets/<sha256 of source>/, content-keyed exactly like stage 0's own.
    • compileAsset

      static Path compileAsset(Path asset, Path classpath)
      Compiles an asset once per content hash, or returns null to source-launch it.

      An unwritable cache is a correct configuration, not an error, so it compiles into a temporary directory instead and the classes simply do not survive the command. Null is reserved for the case where nothing can be compiled at all -- no compiler in this runtime, or source that does not build -- which the caller turns into a diagnostic.

      The bytes were digest-verified before they got here, so what is compiled is what was published.

    • selfCompile

      static void selfCompile(Path source)
      Compiles this source into the cache so the shim can skip the JEP 330 source launch next time. The shim -- not stage 0 -- consults this cache: stage 0 is already the running process by the time it could decide. That is the one place the shim is allowed to know a cache layout, and it is why the cache path is a versioned interface between the shim and stage 0.
    • deleteTree

      static void deleteTree(Path p)
    • wrapperVerb

      static void wrapperVerb(String verb, List<String> rest, Path root, flixw.Lock lock, Path jar, flixw.Jvm jvm, List<String> compilerVerbs, String verbId)
    • dispatchLocal

      static void dispatchLocal(Path root, flixw.Lock lock, Path jar, flixw.Jvm jvm, boolean forExample, List<String> rest, List<String> compilerVerbs, String verbId)
      The local asset serves both local and examples local.
    • readLocalOverrides

      static Map<String,String> readLocalOverrides(Path root)
      The coordinates .flixw/local/packages.toml names, for check's advisory line -- not a general reader. The full format (including each entry's path) lives once, in flixw-local.java, which owns writing it too; this reads only the table headers, since that is all a doctor line needs to say.
    • newerAsset

      static String newerAsset(String source, String have)
      The same asset in that repository's newest release, or null if there is no newer one.

      Derived from the URL the lock already records rather than from anything the plugin declares. That URL is known to have worked, and between two releases of one project only the tag changes -- so substituting the tag asks for the file that corresponds to the one already installed, rather than guessing at a naming scheme.

      Only github.com release assets. A plugin hosted anywhere else gets a diagnostic naming the install command, because inventing a URL for an unknown layout is how an upgrade quietly fetches the wrong artifact.

    • latestTag

      static String latestTag(String repo)
      The tag /releases/latest redirects to, which needs no API token or quota.
    • strip

      static String strip(String tag)
      v1.2.3 and 1.2.3 name one release; the lock records the second.
    • pluginUpgrade

      static void pluginUpgrade(Path root, List<String> args)
      Moves every plugin this project declares, or one named, to its newest release.

      Goes through `plugin install`, so an upgrade is not a second implementation of installing: the digest is taken and recorded, a newly declared verb is checked against the compiler's and the wrapper's, and the unaudited-code warning is printed by the same code that prints it on a first install.

    • pluginsDir

      static Path pluginsDir()
    • pluginCacheDir

      static Path pluginCacheDir(String name)
      Where a plugin may keep derived data between runs.

      Added because a plugin that computes something expensive has nowhere to put it, and the obvious place is wrong: plugins/<name>/ is enumerated by plugin list as the set of installed versions, so a cache directory there is reported as a version that cannot be run. Every plugin that needed this would otherwise invent its own corner of the cache, and none of them would be collected by wrapper --purge.

      flixw promises the path and that --purge collects it. It promises nothing about the contents: this is the plugin's, to key and invalidate as it sees fit. The directory is not created here -- a plugin that never writes leaves no trace.

      Not versioned, deliberately. A plugin upgrade that changes what it derives should say so in its own keys, which it can do and flixw cannot; keying the directory by version would instead orphan the old data silently on every upgrade.

    • pluginDir

      static Path pluginDir(String name, String version, String sha256)
    • pluginInstall

      static void pluginInstall(Path root, List<String> args)
      The only path a plugin's bytes reach the machine -- explicit, one attempt, same shape as acquire(flixw.Lock) for the compiler. `https://` is verified the ordinary way; `file://` copies a local path directly, for local plugin development and for testing this without a public URL. Neither is fetched because a lock named it: a project's [plugins.<name>] entry is read only by resolvePlugin(java.lang.String, flixw.Lock), never by this, so nothing about running `pin` or `doctor` can trigger a download here.
    • installedPlugins

      static Map<String,flixw.PluginDep> installedPlugins()
      Every installed plugin, newest version first, as the cache knows it.

      Name and version come from the directory; the source URL from the file install wrote beside the artifact. A plugin installed before that file existed has no source, and upgrade says so rather than guessing at a URL.

    • commandOwner

      static String commandOwner(flixw.Lock lock, String verb)
      Which plugin, if any, declared verb in this lock.
    • installedCommandOwner

      static String installedCommandOwner(String verb)
      The installed plugin that claims this verb, for a project that declares none.

      A plugin is a tool, not a dependency: installing one is a thing a person does to their machine, like putting a `git-` subcommand on PATH, and having to repeat it in every project's lock to type the word is not what anyone means by "installed". A lock entry is still consulted first, and still pins a version -- that is what a project reaches for when it wants the same plugin on someone else's machine and in CI.

      Read from a file the install wrote beside the artifact, so an unrecognised word costs a directory listing rather than opening every installed plugin's jar.

      Two plugins claiming one word is not resolved here. Install refuses a verb another plugin declared, so this is reachable only across projects that installed different plugins at different times, and picking one silently is worse than saying so.

    • backfillCommand

      static void backfillCommand(Path versionDir, Path into)
      Writes the verb an already-installed plugin declares, reading its jar once.

      An empty file when it declares none, so the read is not repeated on every unrecognised word for every plugin that will never claim one.

    • dirsIn

      static List<Path> dirsIn(Path dir)
      Directories directly under dir, sorted; empty when it is not one.
    • runDeclaredPlugin

      static void runDeclaredPlugin(String name, String verb, List<String> args, Path root, flixw.Lock lock, Path jar, flixw.Jvm jvm)
      Runs the plugin that declared this verb, saying which one answered.

      Louder than ./flixw plugin <name> on purpose. There the caller named the plugin and knows what is running; here a bare word did, and the reader of a build log needs to be told that a third-party artifact -- not the compiler, not the wrapper -- is what answered it.

    • acceptPluginCommand

      static String acceptPluginCommand(String name, String verb, Path root)
    • pluginAttribute

      static String pluginAttribute(Path artifact, String attr, int max, String format)
    • sanitize

      static String sanitize(String s, int max)
      A manifest value made safe to print and to store.

      It is third-party text on its way to a terminal and to a committed file, so control characters go first: an ESC in a description is a terminal escape sequence in every `flixw help` that renders it, and nothing legitimate needs one. Whitespace collapses because the value crosses into the help asset on a tab-separated line, and the length is capped because a manifest attribute has no bound and a lock file is read by people.

    • recordPluginInLock

      static void recordPluginInLock(Path root, String name, String version, String sha256, String url, String description, String command)
    • rootIfAny

      static Path rootIfAny()
      This project's root, or null when there is no project here.
    • lockIfAny

      static flixw.Lock lockIfAny()
    • pluginList

      static void pluginList(flixw.Lock lock)
    • pluginRemove

      static void pluginRemove(List<String> args)
    • pluginVersionOf

      static String pluginVersionOf(Path dir)
      The version half of a <version>-<sha256> plugin directory name.
    • resolvePlugin

      static flixw.ResolvedPlugin resolvePlugin(String name, flixw.Lock lock)
    • findPluginArtifact

      static Path findPluginArtifact(Path dir)
    • cmdQuote

      static String cmdQuote(String arg)
      cmd.exe's own quoting convention for one command-line word: wrap in double quotes if it needs it, doubling any quote already inside. Not a full re-implementation of cmd.exe's parser -- nothing short of one is -- just enough that a space or an embedded quote in a task argument survives as one word in the common case.
    • runTask

      static void runTask(String command, List<String> extraArgs)
      Runs a task's shell string via the platform shell, inheriting cwd and the three streams exactly like a plugin or the compiler does. Extra args are appended positionally -- the same contract `npm run` already has, and the reason for `"$@"` rather than string concatenation: an argument containing a space must not become two.
    • askedVersion

      static String askedVersion(flixw.Lock lock)
      What the pinned compiler reports of itself, as pin recorded it; null when a lock predates the key and no refresh has backfilled it.

      Read rather than asked: the value is a property of the pinned bytes, so the digest every run already checks is what keeps it honest. Says nothing about a FLIX_JAR override -- see reportOverrideGap(flixw.Lock, java.nio.file.Path) for those bytes.

    • report

      static void report(Path root, flixw.Lock lock, Path jar, flixw.Jvm jvm, List<String> cv, String reported)
    • listCache

      static void listCache(flixw.Lock lock, flixw.Jvm jvm)
      The cache inventory info --verbose prints, rendered by a companion asset.

      Stage 0 resolves the JDKs and hands them over; the asset walks the cache and formats. That split is the point: enumerating JDKs runs java -version over a search path and decides what counts, which is selection policy -- a second copy of it could disagree with the one that actually picks the JVM, and the table a person reads would be the one that never runs. Walking documented cache paths decides nothing, so it goes where the formatting is.

      Never fatal. Concise info has already printed by the time this is reached, and a wrapper that could not describe its own state without a network would be worse than one that describes less of it.

    • purgeCache

      static void purgeCache(int days, boolean yes)
      Runs the cache lifecycle half of the inspector without requiring a project.

      This is explicit and age-based rather than automatic: the cache is shared by projects, and a wrapper looking at one checkout has no authority to decide which other checkout may need offline bytes tomorrow. A caller who chooses a retention window is making that trade-off deliberately. The inspector protects the default JDK, this release's companion assets and all stage-0 classes; it considers the its own per-artifact usage record for everything else.

    • inspectContext

      static String inspectContext(flixw.Lock lock, flixw.Jvm jvm)
      What the inspector is told, as opposed to what it looks up.

      Line-based rather than JSON: both ends ship in one release, neither has a parser, and a reader for six fields and three tables would cost more than the format documents. Tabs separate columns, so no value here may contain one -- versions, digests and paths do not.

    • cachedJdkDirs

      static List<Path> cachedJdkDirs()
      <cache>/jdks/*: the trees the provisioner unpacked.
    • printAligned

      static void printAligned(List<String[]> rows)
      Prints a two-column list with its first two columns aligned, so that a listing whose entries vary wildly in length -- 0.75.2 beside 0.75.3+stable.names.4 -- reads as a table instead of a ragged column of annotations nobody can scan.
    • pad

      static String pad(String s, int width)
    • humanSize

      static String humanSize(long bytes)
      A byte count as a person reads it; cached JARs and JDKs are always well above 1 KB.
    • checkCanonical

      static int checkCanonical(Path file, String wantDigest, String label)
      Compares an installed file with the bytes this release ships, by digest.

      By digest rather than by text because the text is no longer here -- it is in the installer asset. The check is the same check: the answer to "is this the shim flixw wrote" is identical whether it comes from comparing 6KB of shell or 64 hex digits, and this way it needs no fetch. What is lost is the ability to say *how* it differs, which this never said anyway.

    • canonicalAttrs

      static String canonicalAttrs(String shipped)
      The line endings the flixw block pins for one shipped path.
    • changesEndings

      static boolean changesEndings(String attrs, String shipped, Map<String,String> macros)
      Does a later rule leave text or eol saying something other than the block does? git resolves attributes one at a time, so a rule reaches only those it names: the block sets linguist-vendored beside the endings, and a project that turns that back off has not touched what this check protects.

      A token that is not text or eol may still be either of them wearing a macro's name, which is why macros is passed rather than assumed empty: binary is git's own, expands to -diff -merge -text, and un-pins the endings while naming neither. Expansion recurses once with no macros, so a macro defined in terms of itself cannot loop.

    • attrMacros

      static Map<String,String> attrMacros(String text)
      The macros in force for this file: git's built-in binary, plus any the file defines itself. Config-defined macros are out of reach and deliberately so — they are not committed, so they describe one clone rather than the project.
    • patternMatches

      static boolean patternMatches(String pattern, String path)
      Does one .gitattributes pattern match one path flixw ships?
    • checkGitattributes

      static int checkGitattributes(Path ga)
      gitattributes resolves by *last* matching pattern, so a rule after the wrapper block silently overrides it -- and a checked-out shim with the wrong line endings is exactly the failure the block exists to prevent. What counts as an override is the resulting attribute, not the mere presence of a later rule: a repetition of what the block already says changes nothing, and neither does a rule setting some unrelated attribute on the same path. Calling either harmful would send someone hunting for a problem they do not have.
    • count

      static int count(String haystack, String needle)
    • git

      static Integer git(Path root, String... args)
      Runs git <args> in root; null when git is absent or the command fails to start.
    • check

      static int check(Path root, flixw.Lock lock, Path jar, flixw.Jvm jvm)
      Every check, printed; the count is the caller's to act on.
    • writeAtomic

      static void writeAtomic(Path file, String text) throws IOException
      Same-directory temp plus an atomic move. A direct write is not a single event: a termination or a power loss in the middle of one leaves a truncated manifest or a half-written lock, and the rollback that was supposed to cover that cannot run either. Renaming a complete file over the old one has no such window.
      Throws:
      IOException
    • restore

      static void restore(Path file, String previous)
      Best-effort rollback; a failed restore must not mask the failure being reported.
    • localCompilerFile

      static Path localCompilerFile(Path root)
      Mutable local state; lock.toml remains the committed, verified fallback.
    • readLocalCompiler

      static flixw.LocalCompiler readLocalCompiler(Path root)
      A malformed selection is fatal, never an implicit switch to another compiler.
    • writeLocalCompiler

      static void writeLocalCompiler(Path root, Path jar, String selectedSha256)
    • clearLocalCompiler

      static void clearLocalCompiler(Path root)
    • selectLocalCompiler

      static flixw.LocalCompiler selectLocalCompiler(Path root, String typedPath)
      Resolves either a built compiler JAR or a Flix checkout containing Mill's output.
    • editorJarPrefsFile

      static Path editorJarPrefsFile(Path root)
      .flixw/local/editor-jar.toml: a per-machine preference for how ./flix.jar -- the file the official VS Code Flix extension checks for at a workspace root, before its own global cache and before it ever downloads anything itself -- is kept in sync with the pin. Not a lock setting and never committed: whether a symlink is even permitted, and which volume the cache lives on relative to the project, both vary by machine, so this belongs beside .flixw/local/java.
    • readEditorJarPref

      static flixw.EditorJarPref readEditorJarPref(Path root)
    • writeEditorJarPref

      static void writeEditorJarPref(Path root, String mode, String sha256)
    • ownsEditorJar

      static boolean ownsEditorJar(Path link, flixw.EditorJarPref pref)
      Whether link is a file this project's own flixw put there -- a symlink resolving into this machine's compiler cache, or a regular file (a copy, or a hard link, indistinguishable from one on disk) whose digest matches what was last recorded here. Anything else is a stranger's file, real or coincidental, and is never silently replaced.
    • ownsEditorJar

      static boolean ownsEditorJar(Path link, flixw.EditorJarPref pref, Path localCompiler)
      Also recognizes a selected local link, including one broken by a clean build.
    • tryEditorJarLink

      static boolean tryEditorJarLink(Path link, Path target, boolean hard)
      Attempts a symlink or hard link; failure lets the caller try its fallback.
    • ensureGitignored

      static void ensureGitignored(Path root, String line)
      One line, appended if missing -- /flix.jar is generated, machine-specific state, the same reason .flixw/local/ itself is never committed. No marked block: unlike .gitattributes, this is one static line with nothing to regenerate, so "already present" is the only question worth asking.
    • maintainEditorJar

      static void maintainEditorJar(Path root, Path jar, String requestedMode)
      Keeps ./flix.jar pointing at exactly the JAR this pin just verified, for the one consumer that looks for it: the official VS Code Flix extension, which checks a workspace root before its own global cache and before it ever downloads anything. flixw itself never reads this file.

      A symlink is tried first, always, regardless of any recorded preference -- it costs nothing to keep current, and once a machine's symlink privilege changes this silently upgrades it off the copy fallback with no separate action. A hard link is tried next, for Windows without that privilege, but only succeeds on the same volume as the cache, which a project directory has no reason to be. Only once both fail does this ever fall back to a plain copy, which can go stale the moment a future pin moves the digest -- doctor checks a copy's digest against the current pin for exactly that reason. requestedMode is --editor-jar's value this run, or null; it, and only it, can authorise replacing a file this project's flixw did not create.

    • cachedFor

      static flixw.Cached cachedFor(String repo, String version, flixw.Lock had)
      The cached jar an earlier pin or run fetched for exactly this repository and version, or null -- and then pin downloads, as it always did.

      Re-pinning used to download unconditionally, because flix.jar has no published checksum and the download was the only source of the digest. The cache already holds both halves of that answer: the jar's name carries its digest, and fetchedFrom(java.lang.String) lists the URLs a download actually produced those bytes from. So a second project, or the same one re-pinned, needs no network -- and a GitHub hiccup can no longer fail a pin whose bytes are already on disk. pin still re-hashes the jar before trusting its name.

      A jar qualifies only if the URL this pin would fetch is among them: upstream's one constructed URL, or either of a fork's two asset names under the exact tag. When several builds of one tag are cached -- an asset replaced under the same tag, both pinned with --fetch -- the one the lock already names wins, and without that there is nothing to choose by: null, download.

    • pin

      static void pin(Path root, flixw.Pin what)
    • updateWrapper

      static void updateWrapper(Path root)
      doctor --fix: the asset rewrites the files it owns, stage 0 refreshes the lock, and the two counts are added up here.

      Split that way because the lock is stage 0's alone -- it is the one file in the project whose *meaning* the wrapper has to understand, and `refreshLock` rewrites it from values it has already validated. Handing that to the installer would give a fetched asset write access to the trust root.

    • warnMissingJava

      static void warnMissingJava(String javaPin)
    • tomlEscape

      static String tomlEscape(String s)
      The two escapes a TOML basic string needs. Every other lock value is pattern-checked into a shape that cannot contain either, so this exists for the one free-text key: a description comes from a plugin's manifest and is only whitespace-collapsed.
    • lockText

      static String lockText(String wrapper, String repo, String version, String url, String sha256, String reportedVersion, String java, Map<String,flixw.PluginDep> plugins)
    • refreshLock

      static flixw.Refresh refreshLock(Path root) throws IOException
      Throws:
      IOException
    • refreshPin

      static void refreshPin(Path root)
      `./flixw pin --refresh`. Offline: the compiler is not re-resolved, not re-downloaded and not re-hashed, and the pin does not move. What changes is the file's shape -- the `#:schema` line a lock written before it existed does not carry, the recorded wrapper version, the layout -- which is why it is a form of `pin` and not of `upgrade`.
    • olderOrSame

      static boolean olderOrSame(String a, String b)
      Is `a` no newer than `b`? Both are the wrapper's own dotted versions.
    • num

      static int num(String s)
    • latestBase

      static String latestBase()
      Where wrapper --upgrade looks for the newest release.

      latest/download resolves without asking an API anything. Overridable the same shape FLIXW_ASSET_SOURCE gives the companion assets, and for the same reason: without it this path cannot be tested at all. The suite could assert that upgrade declines to walk backwards and nothing else, so the half that actually moves a project -- fetch, verify, refuse a downgrade, hand the verified bytes to the setup asset -- ran for the first time only in somebody's project.

      That is the worst place for a first run: upgrading is the one command whose failure leaves a user with no way forward, because the way forward *is* upgrading.

      Note that GitHub's latest skips pre-releases, so a 0.x pre-release is deliberately not offered here -- an adopter asks for one by tag, or by --pre-release (see latestPrereleaseTag()) for whichever is newest.

    • releaseBase

      static String releaseBase(String version)
      Where one release's assets live: a named version's, or whatever latest means.

      FLIXW_RELEASE_SOURCE still wins over both. It names one release outright -- a mirror, or a staging directory during a test -- so a version argument has nothing left to select, and quietly appending one to it would ask for a release inside a release.

    • latestPrereleaseTag

      static String latestPrereleaseTag()
      The tag_name of the single most recently published flixw release, whether or not it is a pre-release -- which is the one thing /releases/latest cannot say, by GitHub's own design.

      --upgrade --pre-release exists because releaseBase(java.lang.String)'s latest shortcut needs no API quota but is exactly the thing that stays blind to a release still finishing verify in .github/workflows/release.yaml: published as a pre-release, promoted only once the regression suite passes. Reaching it before promotion means asking a different endpoint, one that does not filter -- the releases API returns newest-first by creation date, so the first entry is the one wanted regardless of its own pre-release flag, and per_page=1 keeps the response to that one object.

      Extraction is a single regex rather than a JSON parser: stage 0 has no dependency to spend on one, and a release object is large -- pulling one field out of it costs less than reading the rest to throw it away. extractTagName(java.lang.String) is the pure half of this, split out so the parsing can be asserted without a live request.

    • extractTagName

      static String extractTagName(String json)
      The first "tag_name" value in a GitHub releases API response, or null.
    • digestFor

      static String digestFor(String sums, String assetName)
      The digest a SHA256SUMS file names for one file, or null if it does not.
    • sumsName

      static String sumsName(String field)
      The file name in one SHA256SUMS line.

      GNU coreutils marks a file it read in binary mode with a * before the name, and on Windows that is the default -- so a mirror whose digests were generated there lists *flixw.java, and an exact comparison finds nothing. The failure is then "no digest for flixw.java" while the digest is plainly in the file.

      flixw's own releases are built on Linux and never carry the marker; this is for everyone else's.

    • publishedAssets

      static List<String> publishedAssets(String sums)
      Every companion asset a release publishes, read out of that release's own SHA256SUMS rather than from a list in here.

      That is the difference between warming the assets this stage 0 knows about and warming the ones the release actually has. An upgrade runs in the *old* stage 0, which cannot know what the new release added -- so a hard-coded list would silently stop warming the day a fourth asset shipped, and nothing would report it. Reading the manifest means an older wrapper warms assets it has never heard of.

      flixw.java itself is excluded: it is the wrapper, not a companion to it, and the upgrade installs it by a different route.

    • warmAssets

      static int warmAssets(String sums, String version)
      Fetches and verifies every companion asset of one release into the cache, so that the commands needing them work offline afterwards.

      Best-effort per asset, and never fatal. Warming is an optimisation on a command that has already done its real work: an upgrade that installed a new stage 0 and then failed to pre-fetch a completion generator has still upgraded, and the asset will be fetched on demand the first time it is wanted. Failing the upgrade over it would turn a slow network into a broken wrapper.

      Returns:
      how many assets are now cached and verified for that version
    • upgradeWrapper

      static void upgradeWrapper(Path root, String to, boolean pre)
      Moves this project to the newest published flixw -- latest's pick, a version named outright, or, with pre, the newest release regardless of its own pre-release flag (see latestPrereleaseTag()). The old `--upgrade` rewrote the files from the stage 0 already in the tree, which is a repair rather than a version change -- so it printed a note on every run explaining that it had not done what its name says. That repair is now `./flixw doctor --fix`, and this does what the word means. The new stage 0 installs itself. It is the only thing that knows its own shim bytes, and having the old one write files for a version it has never seen is how the two drift apart. The digest is checked against the SHA256SUMS published beside it -- same origin, same TLS, so this catches a corrupted or truncated download and not a compromised release. That is the same footing as the compiler pin, and docs/LIMITATIONS.md says so; a self-update is simply where it matters most.
    • wrapperNamespace

      static void wrapperNamespace(List<String> argv)
      flixw's own namespace. wrapperUsage(java.lang.String) is the one list of what it offers. One verb, and every flixw-only operation under it as a flag. These are not stand-ins for anything Flix might one day ship, so they neither retire nor compete for a name with something that will: `pin`, `info`, `doctor` and `validate` deliberately collide with names Flix could claim, and step aside the day it does. Rewriting flixw's own files, or reporting flixw's own version, never will. Answered before the project, the lock, the network and the compiler, for the same reason the flags it replaces were: a bare verb is subject to compiler-first dispatch, and a compiler that happened to claim `wrapper` would make these unreachable at exactly the moment someone needs them to repair the installation. FLIX_BACKEND does not reach them either.
    • wrapperUsage

      static String wrapperUsage(String problem)
    • completionEarly

      static boolean completionEarly(List<String> args)
      completion, answered before the project is resolved -- and enriched from the cache if there is one.

      It is intercepted this early because the script is what somebody generates while setting up a shell, routinely before any flixw project exists on the machine; requiring one would make the setup step depend on having finished the setup. But a project *is* the interesting case, so the lock and the verb record are read here directly -- both are file reads. Nothing is acquired, launched or downloaded: a completion is not worth a compiler download, and a project whose compiler is not cached yet still gets a working script built from the verbs flixw knows.

      The verb set decides its own dispatch. If a pinned compiler has claimed completion, this stands aside and lets compiler-first routing take it, exactly as a bare wrapper verb would -- which is why the word is not in WRAPPER_VERBS: it is not answered from there, so listing it would advertise a route that does not run.

      Note: because completion runs before compiler acquisition, jar and jvm are passed as null to helpContext(java.nio.file.Path, flixw.Lock, java.nio.file.Path, flixw.Jvm, java.util.List<java.lang.String>, java.lang.String, boolean); flixw-cli.java's tree() model must never probe or launch a subprocess during tree construction.

    • completionShell

      static String completionShell(List<String> args)
      The one shell name in these arguments, validated.

      Both entry points ask this rather than parsing for themselves. They validated separately at first, which is two chances to disagree about what a shell name is, on a command whose whole contract is that it produces the same bytes either way.

    • completionScript

      static void completionScript(List<String> args, Path root, flixw.Lock lock, Path jar, flixw.Jvm jvm, List<String> compilerVerbs, String identity)
      The project-independent completer, on stdout.

      Reached from two places on purpose: early in realMain(java.util.List<java.lang.String>), so it answers with no project at all, and from the wrapper verb, so `./flixw completion fish` inside a project takes the same path and cannot produce different bytes.

    • storedHelp

      static String storedHelp(String identity)
      The stored help for this compiler, re-verified against its own provenance record.

      The digest in .helpmeta is not decoration: this is the one place it is read, and without the check a truncated or edited .help would be rendered as the compiler's own words for as long as that cache entry lived. Returning null re-runs the capture, which is the same answer a missing record already gets.

    • helpContext

      static String helpContext(Path root, flixw.Lock lock, Path jar, flixw.Jvm jvm, List<String> compilerVerbs, String identity, boolean override)
    • esc

      static String esc(String s)
      Escapes a value for the tab-separated rows above.

      Load-bearing for tasks specifically. A task is an arbitrary shell string the project wrote, and nothing stops it holding a tab or a newline -- a lock's decoded \\n reaches here as a real one -- which would split one row into two, or shift every field after it by one. The renderer reverses this, so the two must change together.

    • helpTopic

      static void helpTopic(List<String> rest, Path root, flixw.Lock lock, Path jar, flixw.Jvm jvm, List<String> compilerVerbs, String identity, List<String> jvmOpts, boolean degrade)
      Only interactive help may degrade.
    • renderHelp

      static int renderHelp(List<String> rest, Path root, flixw.Lock lock, Path jar, flixw.Jvm jvm, List<String> compilerVerbs, String identity, List<String> jvmOpts) throws IOException
      Runs the renderer and deletes its context.
      Throws:
      IOException
    • compilerVerbHelp

      static boolean compilerVerbHelp(String verb, String flag, Path root, flixw.Lock lock, Path jar, flixw.Jvm jvm, List<String> compilerVerbs, String identity, List<String> jvmOpts)
      On renderer failure, launch the original compiler argv.
    • renderWrapperHelp

      static void renderWrapperHelp(String verb, String fallback, Path root, flixw.Lock lock, Path jar, flixw.Jvm jvm, List<String> compilerVerbs, String identity, List<String> opts)
    • renderWrapperHelp

      static void renderWrapperHelp(List<String> words, String fallback, Path root, flixw.Lock lock, Path jar, flixw.Jvm jvm, List<String> compilerVerbs, String identity, List<String> opts)
    • offlineHelp

      static void offlineHelp(String identity, Exception e)
      Help with no renderer: the routing table, then the compiler's own captured help.

      This is the path when the asset or picocli cannot be fetched, and it must not be worse than what ./flixw help did before there was a renderer at all -- that printed the wrapper's table *and* the compiler's, and it worked on a cold network because the compiler was already on disk. Both halves survive here, and the compiler half now needs no subprocess either: it is the text the last capture stored, verified against its own digest on the way out.

    • runSetupAsset

      static void runSetupAsset(List<String> args)
      Runs the installer asset, fetching and verifying it first.

      A child process rather than an in-process call, for the same reason the JDK provisioner is one: it is a separate program with its own diagnostics, and its exit status is the answer, and its messages go to this process's own streams.

    • assetSourceBase

      static String assetSourceBase(String version)
      Wrapper-owned, not a plugin -- fetched and verified against the release this stage 0 itself is, never installed by a user and never carrying the "3rd-party, unaudited" warning a real plugin does. Overridable the same shape FLIX_DIST_URL gives the compiler's own distribution base: a self-hosted mirror, or (unset in production) a local fixture for this project's own tests.
    • assetDir

      static Path assetDir(String version)
      Version-keyed, so a release's assets can be cached forever: the entry cannot go stale except by a new release, which is exactly when this path moves.
    • readSums

      static String readSums(String base)
      The digest manifest for one release base, from wherever that base points.

      A file:// base is not a mirror, it is this project's own tests standing a release up locally -- and the JDK HTTP client refuses the scheme outright rather than falling back, so the two cases cannot share one code path. Factored out because they went out of step once already: warming read the manifest with httpGet while fetching read it with this, so `install` threw a raw IllegalArgumentException with no FLIXW code the first time it ran against a fixture.

    • fileUrlHint

      static String fileUrlHint(String base)
      Says what is wrong with a file:// URL that a shell wrote and a JVM cannot read.

      Git Bash reports paths as /d/a/proj, which is meaningful to it and to nothing else: file:///d/a/proj resolves to \d\a\proj on the current drive, and nothing is there. The failure is then a diagnostic naming a path the user never typed, in a form they do not recognise, with no clue that the shell rewrote it.

      Only offered when it applies -- a Windows path with no drive letter -- so it does not become noise on every unreadable file.

    • ensureAsset

      static Path ensureAsset(String name)
    • ensureAsset

      static Path ensureAsset(String name, String version)
      The version is a parameter because wrapper --upgrade warms the assets of the release it is upgrading *to*, from the stage 0 it is upgrading *from*. Everything else asks for its own.
    • main

      public static void main(String[] args)
      The one entry point. Every failure inside is a flixw.Fail, which carries both the FLIXWnnn code printed on stderr and the advisory exit status; nothing else writes an exit status, so a code the user's own program returns cannot be confused with one of ours by accident of where it was thrown.
      Parameters:
      args - the wrapper's argv, passed on to the compiler unchanged when dispatch decides the compiler owns them
    • realMain

      static void realMain(List<String> argv)
    • exactCompilerHelp

      static boolean exactCompilerHelp(List<String> argv)
    • isHelpFlag

      static boolean isHelpFlag(String arg)
    • routingNotice

      static void routingNotice(String verb, String compilerVersion)
      Which side handled a verb, under FLIXW_TRACE only. It used to print on every wrapper-handled command, and it told the caller what they had already said: typing `./flixw doctor` and being told that doctor went to the wrapper is not news. Worse, it read as a warning -- something had happened worth mentioning -- when nothing had. The hot path was already silent; now the rest is too, and the routing is still visible to anyone debugging it.
    • wrapperHelp

      static void wrapperHelp()
      The last-resort help when the renderer itself cannot be reached.
    • sourceLaunchPath

      static Path sourceLaunchPath()
      The .java file this stage 0 was launched from, or null when it is running as the compiled class out of the cache. A source launch knows its own path, and that knowledge outranks FLIXW_SOURCE. FLIXW_SOURCE is the shim telling the *compiled* stage 0 which source it was built from; it says nothing about a stage 0 launched by path. `wrapper --upgrade` hands its environment to a freshly downloaded stage 0 in a temporary directory, which is a different file in a different project-less place -- and believing the inherited variable there anchored the new wrapper in the old project, where a lock exists, so `install` was no longer first contact and went to the compiler: `Unrecognized file extension: 'install'.` Every upgrade failed that way.
    • selfSource

      static Path selfSource()
    • recordJava

      static void recordJava(Path root, Path exe)
      Leaves the shim a note saying which JDK this project resolved to, so the next run can start on it instead of starting on whatever `java` is first on PATH and then relaunching. Without it a project that pins a Java the machine does not use by default pays a whole extra stage 0 -- about 100ms -- on every command. Machine-specific, therefore not committed: `.flixw/.gitignore` keeps `local/` out of git, and flixw writes that file itself rather than editing the project's own .gitignore. It names an executable the shim will run, which sounds like a new trust boundary and is not one: anyone who can write `.flixw/local/` can edit `./flixw` itself, which is simpler and does more. Every failure here is discarded. A read-only checkout, a directory someone deleted, a race with another run -- none of it is worth a diagnostic for a cache whose only job is to save a process start, and whose absence is already the old behaviour.
    • relaunch

      static boolean relaunch(flixw.Jvm jvm, List<String> argv)
      At most one relaunch, guarded by an env marker, so a stale release file cannot loop.
    • awaitWithReaper

      static int awaitWithReaper(Process p) throws InterruptedException
      Waits for a child that owns the terminal, and guarantees it dies with us. Java has no exec(2): stage 0 must stay resident for the child's whole life. The child keeps the terminal, so SIGINT reaches it through the foreground process group. The hook covers the rest: without it, a SIGTERM to stage 0 orphans a compiler that then runs forever. SIGKILL still orphans it -- no Java code can prevent that, and the README says so. The relaunch path shares this. It used to wait bare, so terminating a stage 0 that had relaunched itself into another JVM orphaned the entire subtree beneath it -- the same defect the compiler launch had a hook for, one process further down.
      Throws:
      InterruptedException
    • javaHomeOf

      static Path javaHomeOf(Path exe)
      exe's JDK home: two directories up from bin/java. Null when exe has no parent, which cannot happen for a real path but costs nothing to guard.
    • pluginEnv

      static Map<String,String> pluginEnv(Path root, flixw.Lock lock, flixw.Jvm jvm, Path compilerJar, String pluginName, flixw.ResolvedPlugin p, List<String> args)
      The context a plugin ABI version 1 promises: a flat environment-variable tier for the common case, plus FLIXW_CONTEXT naming a versioned JSON file for anything structured. Both are built here, once, for every format -- .flix included: stock Flix's Sys.Env.getVar reads these even though a .flix plugin cannot receive args (verified against a real compiler, not assumed), so the ABI is the one thing every format can rely on regardless of whether it can take CLI arguments. Compiler and Java fields are simply absent when this project has no lock yet or no Java was resolved -- a `.jar`/`.java` plugin that does not need a compiler must not be handed a context it has to guess is incomplete.
    • writeContextFile

      static Path writeContextFile(Path root, flixw.Lock lock, flixw.Jvm jvm, Path compilerJar, String pluginName, flixw.ResolvedPlugin p, List<String> args)
      The structured half of the ABI: everything pluginEnv(java.nio.file.Path, flixw.Lock, flixw.Jvm, java.nio.file.Path, java.lang.String, flixw.ResolvedPlugin, java.util.List<java.lang.String>)'s flat variables carry, plus the arguments this invocation was given, as one versioned JSON object. Written to a fresh temp file per invocation and deleted by a shutdown hook -- not a `finally` in the caller, because System.exit(int) does not run one.
    • runArtifact

      static void runArtifact(Path artifact, Path javaExe, Path compilerJar, List<String> args, Map<String,String> env)
      Launches one plugin artifact as an opaque subprocess, inheriting cwd and the three streams exactly like launch(java.nio.file.Path, java.util.List<java.lang.String>, java.nio.file.Path, java.util.List<java.lang.String>) does for the compiler -- three formats, one launcher, because a plugin is not otherwise different from the compiler stage 0 already knows how to run. env is the ABI: everything pluginEnv(java.nio.file.Path, flixw.Lock, flixw.Jvm, java.nio.file.Path, java.lang.String, flixw.ResolvedPlugin, java.util.List<java.lang.String>) built, merged into the child's environment alongside whatever it already inherits. .flix always runs against *this project's own pinned compiler*, never a version the plugin names: a plugin can extend what Flix does here, not choose which Flix does it, so it cannot pull in a second, unverified compiler. A .flix plugin cannot receive args: stock Flix has no run <file> mode -- run "runs main for the current project" and refuses a file argument outright -- so the only way to execute one standalone is the bare-file form (java -jar flix.jar plugin.flix), and there every extra positional word is parsed as one more source file to compile, not a program argument -- verified against a real compiler, not assumed. A .jar or .java plugin wanting arguments is the workaround until Flix's own CLI grows one; every format can still read the ABI's environment variables and FLIXW_CONTEXT.
    • autoRunBoundary

      static List<String> autoRunBoundary(List<String> argv)
      run <word>, with no leading flag and no -- already, can only mean a forgotten forwarding boundary for upstream Flix: unlike check/ test, where the same shape is a legitimate extra file to compile, stock run rejects a bare trailing word outright ("does not support file arguments") rather than trying to load it. That fact was verified against flix/flix, not against every fork or FLIX_JAR override -- a fork's run may legitimately define its own positional operand, and inserting -- in front of it would silently turn that operand into a forwarded program argument instead. Callers gate this on isUpstream(flixw.Lock, boolean); it is not re-checked here; a leading flag is left alone regardless -- telling --entrypoint's value from the boundary needs the same value-taking-option knowledge examples' own splitVerbFlags has, which would mean carrying that parser for every compiler verb here rather than the one place it already earns its keep.
    • launch

      static void launch(Path javaExe, List<String> opts, Path jar, List<String> args)
      Inherit cwd and the three streams; propagate the child's status.