Skip to content

Overhaul the managed JETLS installation and bump the pin to 2026-08-29 - #77

Open
aviatesk wants to merge 5 commits into
mainfrom
avi/fix-managed-installation
Open

Overhaul the managed JETLS installation and bump the pin to 2026-08-29#77
aviatesk wants to merge 5 commits into
mainfrom
avi/fix-managed-installation

Conversation

@aviatesk

Copy link
Copy Markdown
Contributor

This PR ports the stabilization work from the jetls-vscode client's managed installation (which itself started as a port of this extension's) back to zed-julia, and bumps the pinned JETLS release.

Server pin

  • Bump the pinned JETLS release from 2026-08-07 to 2026-08-29.
  • Adapt the pin verification to the jetls version <revision>, julia version <version> output format used by JETLS releases since 2026-08-23. Without this, any pin bump past that release would make every fresh installation fail its post-install verification and permanently break the managed installation.
  • Add a CI check (scripts/check-julia-bounds.sh) that verifies the Julia version bounds in src/julia.rs against the julia compat declared by the pinned release's Project.toml, so a future pin bump cannot silently ship stale bounds.

Server launch

  • Launch the server as julia -m JETLS directly instead of going through the Pkg.Apps-generated shim. The shim exports a hardcoded JULIA_DEPOT_PATH containing only the managed depot, so the server never saw the user depot: workspace dependencies had to be re-fetched and re-precompiled into the managed depot (failing offline), and a user-set depot chain was clobbered.
  • The launch environment now chains the managed depot first, then the user's own chain (a user-set JULIA_DEPOT_PATH, falling back to ~/.julia), then the bundled system depots, and pins JULIA_LOAD_PATH to the managed app environment: writes stay in the managed depot while user-depot packages and precompile caches are reused for analyzing workspace dependencies.
  • Environment variable keys are now matched case-insensitively on Windows, and the shim's ---separated argument protocol for binary.arguments is preserved, so existing launch configurations keep working.

Installation updates

  • Updates previously rewrote the single managed depot in place (while a server in another window could still be running from it) and collected superseded packages immediately with Pkg.gc; a failed or interrupted installation left the depot broken, with manual deletion as the only recovery.
  • Each installation now goes into an immutable generation directory that is published atomically through a current pointer only after the pinned version is verified, so a failed or interrupted update never breaks the installation in use and a retry starts from a clean slate.
  • Pkg.gc is replaced by retention-based cleanup driven by last-used markers: unpublished generations are reclaimed after 24 hours, superseded generations after 7 days, and runtime containers of a Julia no longer in use after 30 days. Depots from the old layout age out through the same policy.
  • Runtime containers are now named v<minor>-<hash> (e.g. v1.12-d46d6d4d) instead of an opaque 16-hex-digit hash, making the storage layout inspectable.

Notes

  • The first server start after this update reinstalls JETLS from scratch (new pin and new storage layout); superseded storage is reclaimed automatically after the retention periods, and manually deleting jetls-depots remains safe at any time.
  • Deliberate deviations from jetls-vscode, with rationale in code comments: no install lock (concurrent installs merely duplicate work and the last publish wins), no install-stamp fast path (Zed cannot observe server failures to invalidate a stamp, so the per-start jetls version probe keeps corruption self-healing), and synchronous cleanup (extensions have no resident process).

Base automatically changed from avi/update-README to main August 29, 2026 23:47
@aviatesk
aviatesk force-pushed the avi/fix-managed-installation branch from c86fe21 to 9beb2c6 Compare August 29, 2026 23:47
aviatesk and others added 5 commits August 30, 2026 11:33
JETLS releases since 2026-08-23 report themselves as
`jetls version <revision>, julia version <version>`, while
`is_pinned_jetls_version` only accepted the pre-2026-08-23
`JETLS version <revision> ...` line. Bumping the pin past that
release would therefore make every fresh installation fail its
post-install verification and leave the managed installation
permanently broken.

This change bumps the pin to the latest release (2026-08-29) and
rewrites the parser for the new format, mirroring the semantics of
the jetls-vscode client: the output must contain exactly one
`jetls version ` line, and its comma- or whitespace-terminated
revision token must equal the pin. The old format is now rejected,
so a stale pre-2026-08-23 installation triggers a reinstall as
intended.

The pinned release declares `julia = "1.12.2 - 1.13"` compat,
matching the existing version bounds, so they stay unchanged. The
tests cover the new format and the rejection of the old format,
foreign revisions, and outputs with multiple version lines.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Julia version bounds in `src/julia.rs` must mirror the `julia`
compat entry declared by the pinned JETLS release's Project.toml, but
that sync was only guarded by a source comment. Bumping the pin while
leaving the bounds stale would ship an extension that admits Julia
versions the pinned server does not support, or rejects ones it does.

This change ports the bounds check of the jetls-vscode client:
`scripts/check-julia-bounds.sh` extracts the `JETLS_REPOSITORY`,
`JETLS_REVISION`, and bound constants from `src/julia.rs`, reads the
pinned revision's Project.toml through the GitHub contents API via
`gh api`, and fails unless the compat entry equals
`<lower> - <upper>` exactly. A new `check-julia-bounds` CI job runs
the script on every push and pull request.

The constants stay in `src/julia.rs` as the single source of truth;
the script requires exactly one match per constant, so drift in the
declarations themselves also fails the check rather than being
silently misread. The check passes for the current 2026-08-29 pin,
and the mismatch and unknown-revision failure paths were exercised
manually.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Pkg.Apps-generated shim exports a hardcoded `JULIA_DEPOT_PATH`
containing only the managed depot, so the server never saw the user
depot: packages and precompile caches in `~/.julia` (or a user-set
`JULIA_DEPOT_PATH` chain) were invisible, workspace dependencies had
to be re-fetched and re-precompiled into the managed depot (failing
offline), and any user depot configuration was clobbered.

This change stops launching through the shim and spawns
`julia -m JETLS` directly, mirroring the jetls-vscode extension's
`serverLaunchEnvironment`: `JULIA_DEPOT_PATH` places the managed
depot first, then the user's own chain (falling back to `~/.julia`
via `HOME`/`USERPROFILE`, with a trailing empty entry appending the
bundled system depots), and `JULIA_LOAD_PATH` pins the managed app
environment. Writes stay in the managed depot while user-depot
packages and caches are reused for analysis. The shim's argument
protocol (arguments before `--` go to `julia`, the rest to the app)
is reproduced, so existing `binary.arguments` settings keep their
meaning, and the version check runs through the same direct launch
path it verifies. The shim file now only marks a completed
installation; the install and gc environments also append the
bundled depots so stdlib caches are reused.

As a side effect, Windows no longer launches through `jetls.bat`,
removing the batch intermediary that could orphan the child `julia`
process. `JULIA_APPS_JULIA_CMD` is still honored for selecting the
Julia executable but is no longer pinned into the launch
environment. Unit tests cover the depot chain construction and the
argument protocol, and the direct launch was verified end-to-end
against an existing managed depot.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Updates rewrote the single managed depot in place while a server from
another window could still be running from it, and then collected the
superseded packages immediately with `Pkg.gc(; collect_delay=Day(0))`.
A failed or interrupted installation left that depot broken, retries
reused the broken state, and the only recovery was the error-message
hint to delete the depot manually. Runtime depots for a Julia the user
stopped using were also kept forever.

This change adopts the generation scheme of the jetls-vscode
extension. `jetls-depots/<runtime-key>` becomes a container of
immutable generation directories plus a `current` JSON pointer that is
published through a uniquely named temp file and a rename, so readers
never see torn content and the last of concurrent publishers wins.
Resolution reads the pointer and probes that generation with the
per-start `jetls version` check as before; a pin mismatch or breakage
installs into a fresh generation, verifies it, writes an
`install-stamp.json`, and only then publishes the pointer. The
published generation is never modified, so a failed installation
strands only its own unpublished directory and a retry starts clean.

`Pkg.gc` is replaced by whole-generation cleanup driven by `last-used`
markers touched on every start: unpublished generations are reclaimed
after 24 hours, superseded published generations after 7 days, and
runtime containers of an unused Julia after 30 days. The sweep refuses
to run outside a `jetls-depots` directory, and depots from the
pre-generation layout age out through the same policy.

Deliberate deviations from jetls-vscode: no install lock (concurrent
installs merely duplicate work and the last publish wins), no install
stamp fast path (the extension cannot observe serve failures to
invalidate a stamp, so the per-start probe keeps corruption
self-healing), and synchronous cleanup (extensions have no resident
process). Unit tests cover pointer publish/resolve and validation, the
grace policy, and the cleanup keep-set; the README tip now describes
atomic updates and retention-based cleanup.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Runtime containers under `jetls-depots` were named by an opaque
16-hex-digit hash of the Julia executable path and minor version
(e.g. `60a6946f0a0c5f8f`), which made the storage layout inscrutable
when inspecting it or reclaiming disk space manually.

This change adopts the `runtimeKey` naming of the jetls-vscode
extension: the container name spells out the Julia minor version and
hashes only the executable path, e.g. `v1.12-d46d6d4d`. The FNV-1a
hash is kept (it stays stable across extension builds) and truncated
to eight hex digits, matching the length vscode uses. The rationale
comment is ported alongside: different installations of the same
Julia version are different builds whose precompile caches must not
share a depot, and the patch version deliberately stays out of the
key so a patch update re-precompiles within the existing generation
instead of reinstalling into a fresh container.

Containers under the old hash-only names are no longer referenced;
the stale-runtime sweep reclaims them after the runtime retention.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@aviatesk
aviatesk force-pushed the avi/fix-managed-installation branch from 9beb2c6 to f0b5282 Compare August 30, 2026 02:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant