From a3a0d9cc0cd690b90d9506b4bc3402a416d14efa Mon Sep 17 00:00:00 2001 From: Frotty Date: Thu, 8 Oct 2026 15:15:49 +0200 Subject: [PATCH 1/3] Dist: dump the base CDS archive when the JDK that is copied has none The macOS distribution copies the JDK instead of running jlink, and assumed that it brings classes.jsa. The Temurin for macOS x64 on CI has none, so assembleSlimCompilerDist failed on macos-15-intel after #1386 (macos-latest, arm64, has one). The task now dumps the archive with java -Xshare:dump when the copied runtime has none, and then starts the runtime with -Xshare:on, which fails without a usable archive, instead of looking for a file name. The JVMs run without JAVA_TOOL_OPTIONS and its siblings: an archive takes on the options of the JVM which writes it, and CI exports -XX:+UseCompactObjectHeaders. --- AGENTS.md | 14 ++++++++----- de.peeeq.wurstscript/deploy.gradle | 32 ++++++++++++++++++++++++++++-- 2 files changed, 39 insertions(+), 7 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index b39ded8d0..1c803676f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -157,11 +157,15 @@ This repository has multiple entry points that may trigger compilation/build beh wurst4vscode starts the server with `-XX:+AutoCreateSharedArchive`: the JVM writes an AppCDS archive next to the compiler jar when the first session ends, and the sessions after it start from it. That archive sits on the runtime's own base archive, which `deploy.gradle` makes (`jlink --generate-cds-archive`; macOS copies a full JDK, which -has one) and `assembleSlimCompilerDist` checks for. Without it the JVM silently runs without any archive, which is -how the earlier attempt went unnoticed. jlink writes the archive with a JVM of its own, which takes on the options it is started with: the -CI exports `JAVA_TOOL_OPTIONS=-XX:+UseCompactObjectHeaders`, so `deploy.gradle` removes it (and the other JVM option variables) for that -step, or only `classes_coh.jsa` is made, which a runtime started without that option cannot use. The extension passes `-Xlog:disable` because the JVM reports archive trouble -on stdout, which is the protocol stream: never print anything of your own there. +usually has one, and `assembleSlimCompilerDist` dumps one with `java -Xshare:dump` when the JDK has none: the Temurin +for macOS x64 on CI does not). `assembleSlimCompilerDist` then starts the runtime with `-Xshare:on`, which fails when +there is no usable archive. Without one the JVM silently runs without any, which is how the earlier attempt went +unnoticed. A base archive is written by a JVM which takes on the options it is started with: the CI exports +`JAVA_TOOL_OPTIONS=-XX:+UseCompactObjectHeaders`, so `deploy.gradle` removes it (and the other JVM option variables) for +the JVMs it starts, or only `classes_coh.jsa` is made, which a runtime started without that option cannot use. Pull-request +CI packages nothing (only pushes to master do, on every host), so a change here is not tested on macOS before it merges. +The extension passes `-Xlog:disable` because the JVM reports archive trouble on stdout, which is the protocol stream: +never print anything of your own there. ### Build-map pipeline (centralized) diff --git a/de.peeeq.wurstscript/deploy.gradle b/de.peeeq.wurstscript/deploy.gradle index 6deed90de..b7a7d5244 100644 --- a/de.peeeq.wurstscript/deploy.gradle +++ b/de.peeeq.wurstscript/deploy.gradle @@ -59,6 +59,7 @@ def jdepsPathProvider = javaHomeProvider.map { home -> def jlinkPathProvider = javaHomeProvider.map { home -> new File(home, "bin/jlink${os.isWindows() ? '.exe' : ''}").absolutePath } +def javaExecutableName = os.isWindows() ? "java.exe" : "java" def jmodsDirProvider = javaHomeProvider.map { home -> new File(home, "jmods").absolutePath } @@ -97,6 +98,27 @@ abstract class JdepsRawTask extends DefaultTask { } } +/** + * Runs the java of a runtime image without the JVM option variables of the build machine: CI exports JAVA_TOOL_OPTIONS + * for its tests, and a base class data sharing archive takes on the options of the JVM which writes it (compact + * object headers make it classes_coh.jsa, which a JVM started without them cannot use). + */ +class RuntimeJava { + static String run(File java, List options) { + def command = [java.absolutePath] + options + def pb = new ProcessBuilder(command) + pb.redirectErrorStream(true) + ["JAVA_TOOL_OPTIONS", "_JAVA_OPTIONS", "JDK_JAVA_OPTIONS"].each { pb.environment().remove(it) } + def process = pb.start() + def output = process.inputStream.getText("UTF-8") + def exit = process.waitFor() + if (exit != 0) { + throw new org.gradle.api.GradleException("`" + command.join(" ") + "` exited with " + exit + ": " + output) + } + return output + } +} + def fatJarProvider = tasks.named("shadowJar").flatMap { it.archiveFile } // Provider tasks.register("jdepsRaw", JdepsRawTask) { @@ -228,10 +250,16 @@ tasks.register("assembleSlimCompilerDist", Copy) { doLast { logger.lifecycle("[dist] Folder ready at: ${distRoot.get().asFile}") def runtime = new File(distRoot.get().asFile, "wurst-runtime") + def java = new File(runtime, "bin/" + javaExecutableName) + // jlink made the archive of the slim runtime, and a full JDK (macOS) normally brings its own, but not every + // build does: the Temurin for macOS x64 on CI has none. -Xshare:dump writes it next to the JVM library. if (!["bin/server/classes.jsa", "lib/server/classes.jsa"].any { new File(runtime, it).exists() }) { - throw new GradleException("The runtime has no base CDS archive (classes.jsa under bin/server or lib/server), " + - "so the language server cannot start from an AppCDS archive.") + logger.lifecycle("[dist] The runtime has no base CDS archive, dumping it") + RuntimeJava.run(java, ["-Xshare:dump"]) } + // -Xshare:on makes the JVM fail when it has no usable base archive, where it would otherwise run without one + // and nobody would notice + RuntimeJava.run(java, ["-Xshare:on", "-version"]) } } From 7b4f3c60abf7136c16fb5b5ef2c500034715278c Mon Sep 17 00:00:00 2001 From: Frotty Date: Thu, 8 Oct 2026 15:15:49 +0200 Subject: [PATCH 2/3] TEMPORARY: package the slim runtime on the macOS runners and Windows in this pull request (remove before merge) --- .github/workflows/dist-check-temp.yml | 52 +++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 .github/workflows/dist-check-temp.yml diff --git a/.github/workflows/dist-check-temp.yml b/.github/workflows/dist-check-temp.yml new file mode 100644 index 000000000..f1664d86e --- /dev/null +++ b/.github/workflows/dist-check-temp.yml @@ -0,0 +1,52 @@ +name: Dist check (temporary, not for merge) + +on: + pull_request: + types: [opened, synchronize, reopened] + +jobs: + package: + name: Package slim runtime on ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [macos-15-intel, macos-latest, windows-latest] + runs-on: ${{ matrix.os }} + env: + JAVA_TOOL_OPTIONS: >- + -XX:+UseCompactObjectHeaders + -XX:+UseStringDeduplication + --enable-native-access=ALL-UNNAMED + GRADLE_OPTS: -Dorg.gradle.daemon=false + defaults: + run: + working-directory: de.peeeq.wurstscript + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: '25' + cache: 'gradle' + - name: Pin Gradle toolchain + shell: bash + run: | + echo "org.gradle.java.installations.paths=${JAVA_HOME}" >> gradle.properties + echo "org.gradle.java.installations.auto-detect=false" >> gradle.properties + - name: What the JDK brings + shell: bash + run: | + echo "JAVA_HOME=$JAVA_HOME" + ls -la "$JAVA_HOME/lib/server" "$JAVA_HOME/bin/server" 2>/dev/null || true + - uses: gradle/actions/setup-gradle@v4 + - name: Package slim runtime + shell: bash + run: ./gradlew packageSlimCompilerDist --no-daemon --stacktrace + - name: What the distribution has + shell: bash + run: | + ls -la build/dist/slim-*/wurst-runtime/lib/server build/dist/slim-*/wurst-runtime/bin/server 2>/dev/null || true + RT="$(ls -d build/dist/slim-*/wurst-runtime)" + env -u JAVA_TOOL_OPTIONS "$RT/bin/java" -Xshare:on -version From 8cbec3a41bbe8f9563a94621de0e7493af7d03b5 Mon Sep 17 00:00:00 2001 From: Frotty Date: Thu, 8 Oct 2026 15:20:39 +0200 Subject: [PATCH 3/3] Remove the temporary packaging workflow --- .github/workflows/dist-check-temp.yml | 52 --------------------------- 1 file changed, 52 deletions(-) delete mode 100644 .github/workflows/dist-check-temp.yml diff --git a/.github/workflows/dist-check-temp.yml b/.github/workflows/dist-check-temp.yml deleted file mode 100644 index f1664d86e..000000000 --- a/.github/workflows/dist-check-temp.yml +++ /dev/null @@ -1,52 +0,0 @@ -name: Dist check (temporary, not for merge) - -on: - pull_request: - types: [opened, synchronize, reopened] - -jobs: - package: - name: Package slim runtime on ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - os: [macos-15-intel, macos-latest, windows-latest] - runs-on: ${{ matrix.os }} - env: - JAVA_TOOL_OPTIONS: >- - -XX:+UseCompactObjectHeaders - -XX:+UseStringDeduplication - --enable-native-access=ALL-UNNAMED - GRADLE_OPTS: -Dorg.gradle.daemon=false - defaults: - run: - working-directory: de.peeeq.wurstscript - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - uses: actions/setup-java@v4 - with: - distribution: temurin - java-version: '25' - cache: 'gradle' - - name: Pin Gradle toolchain - shell: bash - run: | - echo "org.gradle.java.installations.paths=${JAVA_HOME}" >> gradle.properties - echo "org.gradle.java.installations.auto-detect=false" >> gradle.properties - - name: What the JDK brings - shell: bash - run: | - echo "JAVA_HOME=$JAVA_HOME" - ls -la "$JAVA_HOME/lib/server" "$JAVA_HOME/bin/server" 2>/dev/null || true - - uses: gradle/actions/setup-gradle@v4 - - name: Package slim runtime - shell: bash - run: ./gradlew packageSlimCompilerDist --no-daemon --stacktrace - - name: What the distribution has - shell: bash - run: | - ls -la build/dist/slim-*/wurst-runtime/lib/server build/dist/slim-*/wurst-runtime/bin/server 2>/dev/null || true - RT="$(ls -d build/dist/slim-*/wurst-runtime)" - env -u JAVA_TOOL_OPTIONS "$RT/bin/java" -Xshare:on -version