From 270bcb4a602c5e8f4df3ed8bbed9b2c5996d6f7f Mon Sep 17 00:00:00 2001 From: BREW <81044362+b-re-w@users.noreply.github.com> Date: Sun, 18 Oct 2026 12:27:44 +0900 Subject: [PATCH 1/6] Test: The one-executable gate watches a run for a Java runtime, and says what it does not check A native image carries its own runtime, so running it must not look for a Java one. The gate runs hello-awtfree once more, alone, under strace on Linux (every file access) and with DYLD_PRINT_LIBRARIES on macOS (every library loaded), and fails on anything under the runner's JDK or GraalVM, a module image, jvm.cfg, libjvm or libjava, or on macOS a library outside the system. A step of its own states what the gate cannot check: building from source needs a JDK; needing only the platform's linker holds where the renderer arrives prebuilt. --- .github/workflows/extended-native-image.yml | 15 +++++++- extended/native-image/README.md | 7 +++- extended/native-image/check-one-executable.sh | 37 +++++++++++++++++++ 3 files changed, 57 insertions(+), 2 deletions(-) diff --git a/.github/workflows/extended-native-image.yml b/.github/workflows/extended-native-image.yml index 9b04b327f3..2c8e536f41 100644 --- a/.github/workflows/extended-native-image.yml +++ b/.github/workflows/extended-native-image.yml @@ -253,7 +253,7 @@ jobs: sudo apt-get install -y -q --no-install-recommends \ build-essential pkg-config xvfb xauth libgl1-mesa-dev libegl-dev libgl1-mesa-dri \ libx11-dev libxext-dev libxi-dev libxrender-dev libxtst-dev libxrandr-dev libxcursor-dev \ - libfontconfig1-dev libfreetype-dev libdbus-1-dev zlib1g-dev fonts-noto-cjk jq gdb + libfontconfig1-dev libfreetype-dev libdbus-1-dev zlib1g-dev fonts-noto-cjk jq gdb strace - name: GraalVM 25 (Liberica NIK, or upstream where the cell names it) shell: bash @@ -430,6 +430,19 @@ jobs: else bash extended/native-image/check-one-executable.sh "$out" "$RUNNER_TEMP/one-executable" fi + # Said here rather than left out: an application built from source with this plugin needs a + # JDK on the machine that builds it, because Gradle, the Kotlin compiler and native-image + # all run on one. Needing nothing but the platform's linker holds only where the renderer + # arrives already built, as compose-rust's does (its build script downloads it); it cannot + # hold for this build, so this gate does not check it. + - name: What this gate does not check, and why + shell: bash + run: | + echo "The executable checked above needs no Java runtime to run. Building it did need one:" + echo " java: $(java -version 2>&1 | head -1)" + echo " native-image: $("$GRAALVM_HOME/bin/native-image" --version 2>&1 | head -1)" + echo "A build that needs only the platform's linker is one whose renderer arrives prebuilt." + - name: Keep the self-check drawing if: always() uses: actions/upload-artifact@v4 diff --git a/extended/native-image/README.md b/extended/native-image/README.md index 570b4a2edb..cdc8979215 100644 --- a/extended/native-image/README.md +++ b/extended/native-image/README.md @@ -150,7 +150,12 @@ syllable whole, lines broken between words), draws it into a PNG, and presents f Compose content in its window, none at a size other than the window's. `hello-awtfree` is a Compose application with no AWT: the core fork's scene module hosts its content in the GraalVM window, through the window's own surface (`appKitWindowSurface`, `x11WindowSurface`). A run -that does not end within 120 s is stopped with every thread's stack printed. +that does not end within 120 s is stopped with every thread's stack printed. It then runs the +executable once more, watched (every file it touches on Linux, through `strace`; every library +it loads on macOS, through `DYLD_PRINT_LIBRARIES`), and fails if it looks for a Java runtime: +anything under the JDK or GraalVM on the runner, a module image, `jvm.cfg`, `libjvm` or +`libjava`. What the gate does not check is the build machine: building from source needs a JDK, +for Gradle, the Kotlin compiler and native-image, and the gate says so in a step of its own. An executable that needs an ICU data file shows it there: a double click would select one syllable, and lines would wrap inside words. diff --git a/extended/native-image/check-one-executable.sh b/extended/native-image/check-one-executable.sh index 1fd42ff387..6600ea4438 100755 --- a/extended/native-image/check-one-executable.sh +++ b/extended/native-image/check-one-executable.sh @@ -189,4 +189,41 @@ if grep -E '^(java|kotlin|org)\.[A-Za-z0-9_.$]*(Exception|Error)\b' "$log"; then fi [[ "$status" -eq 0 ]] || fail "the executable did not run to the end on its own (exit $status)" +# 5. A native image carries its own runtime, so running it must not look for a Java one: no file +# under a JDK or GraalVM, no module image, no jvm.cfg, no libjvm or libjava loaded. Run once more, +# alone, watched: every file access on Linux (strace), every library loaded on macOS (dyld). +runtime_log="$scratch/runtime.log" +case "$os" in + linux) + command -v strace > /dev/null || fail "strace is not installed" "It is how the run is watched for a Java runtime." + ( cd "$scratch/run" && HELLO_SELF_CHECK="$scratch/again.png" timeout 120 \ + strace -f -qq -e trace=file -o "$runtime_log" "./$name" ) > /dev/null 2>&1 || true + accessed="$(sed -n 's/.*"\(\/[^"]*\)".*/\1/p' "$runtime_log" | sort -u)" + ;; + macos) + ( cd "$scratch/run" && HELLO_SELF_CHECK="$scratch/again.png" DYLD_PRINT_LIBRARIES=1 \ + perl -e 'alarm 120; exec @ARGV' "./$name" ) > "$runtime_log" 2>&1 || true + accessed="$(sed -n 's/^dyld\[[0-9]*\]: <[^>]*> //p; s/^dyld\[[0-9]*\]: //p' "$runtime_log" | awk '{print $1}' | sort -u)" + ;; + windows) accessed="" ;; +esac +[[ "$os" == windows || -n "$accessed" ]] || fail "watching the run recorded nothing, so it proves nothing" "$(tail -5 "$runtime_log")" +java_paths=() +for home in "${GRAALVM_HOME:-}" "${JAVA_HOME:-}"; do [[ -n "$home" ]] && java_paths+=("$home"); done +found="" +while IFS= read -r path; do + [[ -n "$path" ]] || continue + case "$path" in + */lib/modules|*/jvm.cfg|*/libjvm.*|*/libjava.*|*/conf/security/java.security) found+="$path"$'\n'; continue ;; + esac + for home in "${java_paths[@]+"${java_paths[@]}"}"; do + [[ "$path" == "$home"/* ]] && found+="$path"$'\n' + done + if [[ "$os" == macos ]]; then + case "$path" in /usr/lib/*|/System/*) ;; *) [[ "$path" == "$scratch/run/$name" ]] || found+="$path (a library outside the system)"$'\n' ;; esac + fi +done <<< "$accessed" +[[ -z "$found" ]] || fail "running the executable looked for a Java runtime or a library of its own" $found +echo "-- run watched ($( [[ "$os" == macos ]] && echo "every library loaded" || echo "every file accessed")): $(wc -l <<< "$accessed" | tr -d ' ') paths, none in a Java runtime" + echo "ok $name is one executable: system libraries only, Korean laid out with no ICU data file" From 3735e6b786259efd260a035c766ff18a79e88eda Mon Sep 17 00:00:00 2001 From: BREW <81044362+b-re-w@users.noreply.github.com> Date: Sat, 24 Oct 2026 07:28:57 +0900 Subject: [PATCH 2/6] Fix: Watch macOS run without running under perl --- extended/native-image/check-one-executable.sh | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/extended/native-image/check-one-executable.sh b/extended/native-image/check-one-executable.sh index 6600ea4438..3d86c078ea 100755 --- a/extended/native-image/check-one-executable.sh +++ b/extended/native-image/check-one-executable.sh @@ -28,7 +28,9 @@ fi fail() { echo "fail $1" >&2 shift - for line in "$@"; do echo " $line" >&2; done + for arg in "$@"; do + while IFS= read -r line; do [[ -n "$line" ]] && echo " $line" >&2; done <<< "$arg" + done exit 1 } @@ -201,8 +203,15 @@ case "$os" in accessed="$(sed -n 's/.*"\(\/[^"]*\)".*/\1/p' "$runtime_log" | sort -u)" ;; macos) - ( cd "$scratch/run" && HELLO_SELF_CHECK="$scratch/again.png" DYLD_PRINT_LIBRARIES=1 \ - perl -e 'alarm 120; exec @ARGV' "./$name" ) > "$runtime_log" 2>&1 || true + ( cd "$scratch/run" && HELLO_SELF_CHECK="$scratch/again.png" DYLD_PRINT_LIBRARIES=1 exec "./$name" ) > "$runtime_log" 2>&1 & + pid=$! + for _ in $(seq 120); do kill -0 "$pid" 2>/dev/null || break; sleep 1; done + if kill -0 "$pid" 2>/dev/null; then + kill -9 "$pid" 2>/dev/null || true + wait "$pid" 2>/dev/null || true + else + wait "$pid" 2>/dev/null || true + fi accessed="$(sed -n 's/^dyld\[[0-9]*\]: <[^>]*> //p; s/^dyld\[[0-9]*\]: //p' "$runtime_log" | awk '{print $1}' | sort -u)" ;; windows) accessed="" ;; @@ -212,7 +221,7 @@ java_paths=() for home in "${GRAALVM_HOME:-}" "${JAVA_HOME:-}"; do [[ -n "$home" ]] && java_paths+=("$home"); done found="" while IFS= read -r path; do - [[ -n "$path" ]] || continue + [[ -n "$path" && "$path" == /* ]] || continue case "$path" in */lib/modules|*/jvm.cfg|*/libjvm.*|*/libjava.*|*/conf/security/java.security) found+="$path"$'\n'; continue ;; esac @@ -223,7 +232,7 @@ while IFS= read -r path; do case "$path" in /usr/lib/*|/System/*) ;; *) [[ "$path" == "$scratch/run/$name" ]] || found+="$path (a library outside the system)"$'\n' ;; esac fi done <<< "$accessed" -[[ -z "$found" ]] || fail "running the executable looked for a Java runtime or a library of its own" $found +[[ -z "$found" ]] || fail "running the executable looked for a Java runtime or a library of its own" "$found" echo "-- run watched ($( [[ "$os" == macos ]] && echo "every library loaded" || echo "every file accessed")): $(wc -l <<< "$accessed" | tr -d ' ') paths, none in a Java runtime" echo "ok $name is one executable: system libraries only, Korean laid out with no ICU data file" From 8c33f7634571b56ad642e5430fe96874622aed36 Mon Sep 17 00:00:00 2001 From: BREW <81044362+b-re-w@users.noreply.github.com> Date: Sat, 24 Oct 2026 08:05:17 +0900 Subject: [PATCH 3/6] Chore: Bump CORE_EXTENDED_REF to take window preseed fix --- .github/workflows/extended-native-image.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/extended-native-image.yml b/.github/workflows/extended-native-image.yml index 2c8e536f41..f354391a48 100644 --- a/.github/workflows/extended-native-image.yml +++ b/.github/workflows/extended-native-image.yml @@ -23,7 +23,7 @@ permissions: env: # thisisthepy/compose-multiplatform-core-extended, the commit the window modules and # extended/skiko come from. - CORE_EXTENDED_REF: 57b4a0e102d837ba3271699297b69278fcefedef + CORE_EXTENDED_REF: 6a3db31e5d24b6f125a2b16dfd0f81d582a88a03 # The plugin as a local development build, asking for the core fork's Compose at its own # version. The core fork's desktop Compose is built once from CORE_EXTENDED_REF, by the # core-compose job (extended/ci/core-compose), and handed to the jobs that need it. From 28a3827d11b969d87c77a79897233a9bcc48324f Mon Sep 17 00:00:00 2001 From: BREW <81044362+b-re-w@users.noreply.github.com> Date: Sat, 24 Oct 2026 08:16:26 +0900 Subject: [PATCH 4/6] Chore: Use branch name for CORE_EXTENDED_REF to allow shallow fetch --- .github/workflows/extended-native-image.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/extended-native-image.yml b/.github/workflows/extended-native-image.yml index f354391a48..6692406da9 100644 --- a/.github/workflows/extended-native-image.yml +++ b/.github/workflows/extended-native-image.yml @@ -23,7 +23,7 @@ permissions: env: # thisisthepy/compose-multiplatform-core-extended, the commit the window modules and # extended/skiko come from. - CORE_EXTENDED_REF: 6a3db31e5d24b6f125a2b16dfd0f81d582a88a03 + CORE_EXTENDED_REF: feat/publish-one-version-line # The plugin as a local development build, asking for the core fork's Compose at its own # version. The core fork's desktop Compose is built once from CORE_EXTENDED_REF, by the # core-compose job (extended/ci/core-compose), and handed to the jobs that need it. From 0f6ca3863fcae7e037aef7114f8e82a67e76a6d0 Mon Sep 17 00:00:00 2001 From: BREW <81044362+b-re-w@users.noreply.github.com> Date: Sat, 24 Oct 2026 08:39:44 +0900 Subject: [PATCH 5/6] Fix: Preseed Adoptium JDK in native-image workflow for window build --- .github/workflows/extended-native-image.yml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/.github/workflows/extended-native-image.yml b/.github/workflows/extended-native-image.yml index 6692406da9..1be42b233c 100644 --- a/.github/workflows/extended-native-image.yml +++ b/.github/workflows/extended-native-image.yml @@ -23,7 +23,7 @@ permissions: env: # thisisthepy/compose-multiplatform-core-extended, the commit the window modules and # extended/skiko come from. - CORE_EXTENDED_REF: feat/publish-one-version-line + CORE_EXTENDED_REF: 57b4a0e102d837ba3271699297b69278fcefedef # The plugin as a local development build, asking for the core fork's Compose at its own # version. The core fork's desktop Compose is built once from CORE_EXTENDED_REF, by the # core-compose job (extended/ci/core-compose), and handed to the jobs that need it. @@ -185,6 +185,8 @@ jobs: working-directory: core-extended/extended/window run: | set -euo pipefail + mkdir -p ~/.cache/JetBrains/Kotlin/download.cache + curl -fsSL -o ~/.cache/JetBrains/Kotlin/download.cache/bd467c5b70-OpenJDK25U-jdk_x64_linux_hotspot_25.0.5_8.tar.gz "https://github.com/adoptium/temurin25-binaries/releases/download/jdk-25.0.5%2B8/OpenJDK25U-jdk_x64_linux_hotspot_25.0.5_8.tar.gz" || true cp project.yaml project.yaml.all printf 'modules:\n' > project.yaml for m in common scene graalvm/graalvm-macos graalvm/graalvm-linux; do printf ' - %s\n' "$m" >> project.yaml; done @@ -308,6 +310,15 @@ jobs: working-directory: core-extended/extended/window run: | set -euo pipefail + if [ "$RUNNER_OS" = macOS ]; then + cache="$HOME/Library/Caches/JetBrains/Kotlin/download.cache" + mkdir -p "$cache" + curl -fsSL -o "$cache/f8362503fc-OpenJDK25U-jdk_aarch64_mac_hotspot_25.0.5_8.tar.gz" "https://github.com/adoptium/temurin25-binaries/releases/download/jdk-25.0.5%2B8/OpenJDK25U-jdk_aarch64_mac_hotspot_25.0.5_8.tar.gz" || true + elif [ "$RUNNER_OS" = Linux ]; then + cache="$HOME/.cache/JetBrains/Kotlin/download.cache" + mkdir -p "$cache" + curl -fsSL -o "$cache/bd467c5b70-OpenJDK25U-jdk_x64_linux_hotspot_25.0.5_8.tar.gz" "https://github.com/adoptium/temurin25-binaries/releases/download/jdk-25.0.5%2B8/OpenJDK25U-jdk_x64_linux_hotspot_25.0.5_8.tar.gz" || true + fi # The module lists are core-extended's own extended-window-publish matrix. # The samples link the GraalVM window layers, and hello-awtfree hosts a scene in them. # native/macos is the Kotlin/Native window and compiles against the core fork's From e6312f95b7bceeca94c9af784c1320211ac450d1 Mon Sep 17 00:00:00 2001 From: BREW <81044362+b-re-w@users.noreply.github.com> Date: Sat, 24 Oct 2026 08:52:06 +0900 Subject: [PATCH 6/6] Fix: Build common as JVM-only in window-modules and package jobs --- .github/workflows/extended-native-image.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/extended-native-image.yml b/.github/workflows/extended-native-image.yml index 1be42b233c..621e86daab 100644 --- a/.github/workflows/extended-native-image.yml +++ b/.github/workflows/extended-native-image.yml @@ -180,6 +180,8 @@ jobs: run: | perl -0pi -e 's/^ platforms: \[ jvm, macosArm64 \]$/ platforms: [ jvm ]/m' scene/module.yaml grep -q '^ platforms: \[ jvm \]$' scene/module.yaml + perl -0pi -e 's/^ platforms: \[.*\]$/ platforms: [ jvm ]/m' common/module.yaml + grep -q '^ platforms: \[ jvm \]$' common/module.yaml - name: Publish the window modules to mavenLocal shell: bash working-directory: core-extended/extended/window @@ -304,6 +306,8 @@ jobs: run: | perl -0pi -e 's/^ platforms: \[ jvm, macosArm64 \]$/ platforms: [ jvm ]/m' scene/module.yaml grep -q '^ platforms: \[ jvm \]$' scene/module.yaml + perl -0pi -e 's/^ platforms: \[.*\]$/ platforms: [ jvm ]/m' common/module.yaml + grep -q '^ platforms: \[ jvm \]$' common/module.yaml - name: Publish the window modules to mavenLocal if: runner.os == 'macOS' shell: bash