Skip to content

Package the command: jlink image, AppCDS, man pages, completions, release workflow - #41

Merged
david-Darkcollective merged 2 commits into
developfrom
feat/packaging-release
Oct 9, 2026
Merged

david-Darkcollective merged 2 commits into
developfrom
feat/packaging-release

Conversation

@david-Darkcollective

@david-Darkcollective david-Darkcollective commented Oct 9, 2026 •

Copy link
Copy Markdown
Member

Part of #15 (S11). It leaves the issue open until the dry-run release has run from main.

What it adds

  • ./gradlew releaseArchive builds relix-<version>-<platform>.tar.gz (.zip on Windows). It contains:

    • the jlink image, with relix-solver-ojalgo and relix-docs, and picocli and H2 force-merged
    • man/man1, one page per command, from picocli's ManPageGenerator through Asciidoctor's manpage backend
    • completions/ for bash, zsh, fish and PowerShell
    • the licence files

    H2 becomes a runtime dependency: it is the one driver the image bundles.

  • AppCDS. The image carries lib/relix.jsa, trained at build time on top of the JDK archive that jlink generates. The launcher passes -XX:SharedArchiveFile. The JVM's warnings go to stderr instead of stdout, where they would mix with the rows. On macOS arm64, the training run's script started about 19% faster than with -Xshare:off (0.67 s against 0.82 s). Nothing asserts a time.

  • Launcher fixes.

    • The sh launcher resolves a relative symlink, which is how Homebrew's bin.install_symlink installs it.
    • The generated .bat ran pushd %DIR%, so it ran from its own bin directory. Relative script paths and .relix/ discovery broke on Windows. That line is removed.
  • zsh completion now starts with #compdef relix, so it works as _relix on $fpath as well as sourced.

  • ./gradlew packageTest unpacks the archive with the system's tar and runs its launcher. CI now runs it on all three OSes.

    • A script on stdin over a catalog in a temp tree (a CSV source plus an H2 connection, trusted first), with TSV out.
    • OPTIMIZE and relix doc, the providers that jlink would drop without any error.
    • -Xshare:on with cli.Main loaded from shared objects file (top), and -Xshare:on refusing to start when the archive is missing.
    • man -l over every page. macOS's man takes a path instead.
    • The launcher run through a relative symlink.
  • Release workflow (.github/workflows/release.yml) builds and tests the five platform archives: macOS arm64 and x86_64, Linux x86_64 and arm64, Windows x86_64.

    • Tag vX.Y.Z: attaches the archives and checksums.txt to the GitHub Release, then pushes the formula and the manifest to the tap and the bucket. A suffixed tag makes a pre-release and leaves the tap and the bucket alone.
    • Run by hand: a dry run. packaging/release.py writes the formula and the manifest from the templates in packaging/, then fails unless every archive they name has the checksum they give it. The result is uploaded as the release-files artifact.
  • Formula: installs the man pages and the bash, zsh and fish completions. Its test do block pipes a script into relix with a CSV input. Manifest: a post_install adds a guarded line to the PowerShell profile that loads the completion, and the uninstaller removes it. Both declare Apache-2.0 and point at this repository.

Acceptance

  • Smoke tests run the packaged launcher in a pipe (script on stdin, catalog in a temp tree, TSV out). They pass on macOS arm64 locally, and on Linux, macOS and Windows in CI.
  • A test shows the CDS archive is used, with -Xshare:on.
  • The man pages render (man -l on Linux).
  • A dry-run release produces a formula and a manifest whose checksums match the archives. The check passed locally on stand-in archives, and a tampered archive is caught. The workflow itself can only be dispatched once it is on main.

Before the first public release

  • DarkCollective/homebrew-relix and DarkCollective/scoop-relix are public.
  • The HOMEBREW_TAP_PAT and SCOOP_BUCKET_PAT secrets are set here.
  • License and homepage: written from packaging/ on every release.

What remains for #15 is the dry-run release, which can be dispatched once this is on main.

`relix completion zsh` printed bash's script, which zsh runs through
bashcompinit when it is sourced. Homebrew installs zsh completions as a
file on $fpath instead, which compinit binds by its first line and runs
as a function on the first Tab. The script now starts with
`#compdef relix`, and ends by completing the word it was called for when
it runs inside a completion; sourced, it does what it did before.

Part of #15.
…ease

./gradlew releaseArchive builds this platform's archive: the jlink image,
with relix-solver-ojalgo and relix-docs, picocli and the H2 driver
force-merged; man/man1, one page per command from picocli's
ManPageGenerator through Asciidoctor's manpage backend; and completions/
for bash, zsh, fish and PowerShell. H2 is now a runtime dependency, the
one driver the image bundles.

Each stage of a pipeline starts a JVM, so the image carries an AppCDS
archive (lib/relix.jsa), trained at build time on a run that joins and
groups a bound CSV file, over the JDK archive jlink generates. The
launcher maps it and sends the JVM's warnings to standard error rather
than into the rows.

The generated launchers are patched: the sh one resolves a relative
symbolic link, as Homebrew installs it, and the Windows one no longer
runs from its own bin directory, which took relative paths and .relix/
discovery away from the user's.

./gradlew packageTest unpacks the archive with the system's tar and runs
its launcher: a script on stdin over a catalog in a temporary tree (a CSV
source and an H2 connection, trusted first), tsv out; OPTIMIZE and
relix doc, for the providers jlink would drop unnoticed; -Xshare:on with
the command's classes loaded from the archive, and the same flag refusing
a missing one; and man over every page. CI runs it on each platform.

The Release workflow builds and tests the five platform archives. On a
tag it attaches them and checksums.txt to the GitHub Release and pushes
the formula and the manifest, written whole from packaging/, to the tap
and the bucket; run by hand it is a dry run, which checks their
checksums against the archives and publishes nothing.

Part of #15.
@david-Darkcollective
david-Darkcollective merged commit 340496a into develop Oct 9, 2026
3 checks passed
@david-Darkcollective
david-Darkcollective deleted the feat/packaging-release branch October 9, 2026 21:12
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