diff --git a/.github/workflows/extended-native-image.yml b/.github/workflows/extended-native-image.yml index 9b04b327f3..621e86daab 100644 --- a/.github/workflows/extended-native-image.yml +++ b/.github/workflows/extended-native-image.yml @@ -180,11 +180,15 @@ 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 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 @@ -253,7 +257,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 @@ -302,12 +306,23 @@ 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 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 @@ -430,6 +445,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..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 } @@ -189,4 +191,48 @@ 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 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="" ;; +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" && "$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"