Conversation
Train the packaged launcher after jpackage and reference the resulting cache from the launcher configuration, improving jabkit startup time. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X9bGZAT6xPeH3ueCA8PzPW
PR Summary by QodoShip JEP 514 AOT cache with jabkit app images
AI Description
Diagram
High-Level Assessment
Files changed (4)
|
Code Review by Qodo
1.
|
…ent, changelog prefix, space-safe cache path Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X9bGZAT6xPeH3ueCA8PzPW
|
Code review by qodo was updated up to the latest commit 254e26e |
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X9bGZAT6xPeH3ueCA8PzPW
The glibc runtime stage can execute the packaged launcher, so the Docker image ships the cache the Alpine build stage cannot create. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X9bGZAT6xPeH3ueCA8PzPW
|
Code review by qodo was updated up to the latest commit 9e73a50 |
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X9bGZAT6xPeH3ueCA8PzPW
|
🤖 Generated with Claude Code jabgui feasibility check (local Linux, jabgui app image, time until the main window appears under Xvfb, 3 runs):
Training works (SIGTERM does trigger cache assembly). The blocker is the packaging plugin: installers are built from the app image in the same task action, so a |
|
Marked as draft, because devcall labels was put on |
|
Discussion: 30 to 100 MB larger distribution size for 10% more startup speed. Not worth. Because we need much more storage space because of the many distributions. |
Summary
🤖 The
jabkitapp image now ships a JEP 514 ahead-of-time cache: the build does a training run of the packaged launcher and the launcher loads the cache on every start, cutting JVM startup time (locally ~0.4 s per invocation). An unusable cache is ignored at runtime, so nothing breaks on exotic setups.jabref-contrib-policy:4.2:reviewed:okAnalogies: Like honey, the cache is produced once and enjoyed on every later serving; like chocolate, it melts away startup wait without changing the recipe; and like the moon, it silently accompanies every launch without anyone having to steer it.
Steps to test
./gradlew :jabkit:jpackagejabkit/build/packages/<target>/jabkit/lib/app/containsjabkit.aotandjabkit.cfgends withjava-options=-XX:AOTCache=$APPDIR/jabkit.aottime ./bin/jabkit --versionagainstJAVA_TOOL_OPTIONS="-XX:AOTMode=off" time ./bin/jabkit --versionRelated issues and pull requests
Closes https://github.com/JabRef/jabref-issue-melting-pot/issues/947
The GUI (jabgui) is left out on purpose: creating an AOT cache needs a training run of the real launcher, which for the GUI would require driving a display on every packaging runner. Extending this to jabgui is blocked on a packaging-plugin hook between app-image creation and installer packaging; requested upstream in gradlex-org/java-module-packaging#135.
AI usage
Claude Code (model claude-fable-5), AIL3 — implemented by AI, reviewed and owned by the contributor.
AI CHECKLIST.md walkthrough
1. Code self-review
Nullability and control flow — not applicable (no Java code changed, Gradle build script only)
== null/!= nullchecks — JSpecify annotations (@NullMarked,@Nullable,@NonNull) used instead.Objects.requireNonNull(...)— nullability expressed via JSpecify annotations.@NullMarked(org.jspecify.annotations.NullMarked).Optionalconsumed withifPresent/ifPresentOrElse/map/orElseThrow— neverorElse(unusedValue)nor anisPresent()+get()block.StringUtil.isBlank(...)used instead ofs == null || s.isBlank().Exceptions — not applicable (build script only)
catch (Exception e)— only specific exceptions are caught.throw new RuntimeException(...)/IllegalStateException(...)— these tear down the whole application.LOGGER.info("...", e)), not concatenated into the message string.Style and idioms
BibEntryobjects built with withers (withField, notsetField).List.of()/Map.of()/Set.of(),Path.of(),SequencedCollection/SequencedSet, text blocks.Pattern.compile(...)constant, notString.matches(...).org.jabref.logic.util.BackgroundTask, notnew Thread().///) uses Markdown syntax, not JavaDoc inline tags:codeinstead of{@code},[ClassName]instead of{@link}.User-facing text — not applicable (no UI change)
Localization.langin Java,%prefix in FXML).!; labels do not end with:."...: %0"), not string concatenation.Security
text/htmlresponse — including exception/error messages, not just the success body (XSS).Tests
org.jabref.model/org.jabref.logichave added or updated tests.assertEquals), use plain JUnit asserts (not AssertJ), have no@DisplayName, do not catch exceptions (let them propagate so JUnit reports setup/teardown failures directly), and use@TempDirinstead of manual temp directories.2. Verification commands — not applicable except markdownlint (no Java touched);
:jabkit:jpackagebuilt and ran successfully instead./gradlew :jablib:check(or./gradlew checkfor all modules)../gradlew checkstyleMain checkstyleTest checkstyleJmh../gradlew modernizer../gradlew --no-configuration-cache :rewriteDryRunreports no changes (run./gradlew rewriteRunto fix)../gradlew javadoc.npx markdownlint-cli2 "docs/**/*.md" "*.md"(only if Markdown changed).rewriteRun: intellij-format docker.3. Documentation
CHANGELOG.mdentry added if the change is visible to the user (end-user wording, no extra blank lines).docs/requirements/<area>.md(build/packaging change, no functional feature).docs/updated if behavior or architecture changed.4. Pull request
.github/PULL_REQUEST_TEMPLATE.md, every section filled.[x],[ ], or[/].gh pr create --body-file <file>(not--body).CHANGELOG.mdTODOplaceholder replacement (issue link used directly).Checklist
CHANGELOG.mddescribing the change from the user's point of view (if the change is visible to the user)🤖 Generated with Claude Code