Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JDK 21 toolchain #876

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open

Conversation

sgammon
Copy link
Contributor

@sgammon sgammon commented Jan 6, 2025

Summary

This PR updates dependencies and build process for Pkl to bring GraalVM and Truffle support up to their latest versions. Today, Pkl builds against Java 17+, and requires Java 17+ to run.

After merging this PR, Pkl would build against Java 21, test against Java 21, and impose no change to lib consumers (i.e. still only requiring Java 17+ to run or transitively compile against Pkl).

Test downstream here (updated), via Github Actions. On that fork PR, the change set is expressed in full, with fixups.

PR Tree

Rationale

  • Gradle Toolchains: Uses Gradle's toolchains feature to provision and use different versions of the JDK.

  • JDK 21 Toolchain: Builds (and tests) occur under JDK 21, with bytecode targeting JVM 17. Build bytecode adopts JVM 21 to accelerate build tooling with no change to lib consumers.

  • Truffle SVM Dependency: Certain superclasses used by Pkl (notably, AbstractTruffleException and TruffleFeature) have moved to the new org.graalvm.nativeimage:truffle-runtime-svm coordinate.

  • Multi-JDK testing: Adds stronger guarantees for backward compatibility with regard to Pkl and JVM execution (i.e. java -jar ... with a fat JAR). Tests against versions 18-23 with test suites and java -jar ... executions.

Known Issues

  • Failing Tests (pkl-gradle)
  • Exports on JVM17: Running against newer Truffle on JVM17 may need --add-exports=...

Pre-merge Checklist

  • Refresh Gradle lockfiles and review

Changelog

  • feat: support for jvm21+ toolchain
  • feat: support for gradle toolchains
  • feat: pass -PnativeArch=native to build with -march=native
  • test: multi-jdk testing support
  • test: support for jvm-test-suite plugin
  • test: add tasks to run jpkl eval on multiple jdks
  • test: make jdk exec tests respect multi-jdk flags and ranges
  • fix: remove mrjar classes at >jvm17 from fatjars
  • fix: use jdk21 to run the tests (needed for Unsafe.ensureInitialized)
  • fix: truffle svm dependency is required after graalvm 24.0.0
  • fix: warnings for gvm flag usage, renamed truffle svm macro
  • fix: build with --add-modules=jdk.unsupported where needed
  • fix: don't use gu tool for modern graalvm versions
  • fix: catch Throwable instead of deprecated-for-removal ThreadDeath
  • chore: buildinfo changes for JVM targets, toolchains
  • chore: enforce testing at exactly jdk21
  • chore: enforce build tooling at jdk21+
  • chore: bump graalvm/truffle libs → 24.1.2
  • chore: toolchains for buildSrc

@sgammon sgammon force-pushed the feat/jvm21plus-upstream branch from 8c2a05f to dc45756 Compare January 6, 2025 04:52
@sgammon sgammon changed the title feat: jvm 21+ support JVM 21+ support Jan 6, 2025
@sgammon sgammon mentioned this pull request Jan 6, 2025
buildSrc/src/main/kotlin/BuildInfo.kt Show resolved Hide resolved
buildSrc/src/main/kotlin/BuildInfo.kt Outdated Show resolved Hide resolved
buildSrc/src/main/kotlin/InstallGraalVm.kt Show resolved Hide resolved
gradlew Outdated Show resolved Hide resolved
pkl-cli/pkl-cli.gradle.kts Show resolved Hide resolved
pkl-cli/pkl-cli.gradle.kts Show resolved Hide resolved
pkl-cli/pkl-cli.gradle.kts Show resolved Hide resolved
pkl-cli/pkl-cli.gradle.kts Outdated Show resolved Hide resolved
pkl-executor/pkl-executor.gradle.kts Outdated Show resolved Hide resolved
@bioball bioball changed the title JVM 21+ support JVM 22+ support Jan 6, 2025
@bioball
Copy link
Contributor

bioball commented Jan 6, 2025

This is great, thanks, Sam!

I will do a review of this by next week.

FYI: Pkl today supports Java 21. The range enabled here is Java 22+ (updated your PR title).

settings.gradle.kts Outdated Show resolved Hide resolved
@sgammon

This comment was marked as outdated.

@StefMa
Copy link
Contributor

StefMa commented Jan 6, 2025

@sgammon feel free to extract the toolchain support into another PR. Indeed, my PR is quited dated. I will close it...

@sgammon
Copy link
Contributor Author

sgammon commented Jan 6, 2025

@StefMa roger, no worries :) I am a well known offender in that regard

@sgammon sgammon force-pushed the feat/jvm21plus-upstream branch from dc45756 to 1982880 Compare January 6, 2025 22:00
@sgammon

This comment was marked as outdated.

@sgammon

This comment was marked as outdated.

Copy link
Contributor

@bioball bioball left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this!

Also, general note: we'll need to set up our CI to use Java 21 to compile (for various compileJava tasks), and ideally to test with all of JDK 17/21/22+. I actually think toolchains probably makes sense for this, so, I'd be okay with the toolchain changes coming back into this PR.

With toolchains, maybe the version of Java used for tasks.test can come from a build flag.

gradlew Outdated Show resolved Hide resolved
buildSrc/src/main/kotlin/pklFatJar.gradle.kts Show resolved Hide resolved
gradle/libs.versions.toml Outdated Show resolved Hide resolved
pkl-cli/pkl-cli.gradle.kts Outdated Show resolved Hide resolved
buildSrc/src/main/kotlin/pklFatJar.gradle.kts Outdated Show resolved Hide resolved
@sgammon sgammon changed the title JVM 22+ support JDK 21+ toolchain Jan 22, 2025
@sgammon sgammon force-pushed the feat/jvm21plus-upstream branch from 1982880 to a314e92 Compare January 22, 2025 06:12
pkl-cli/pkl-cli.gradle.kts Outdated Show resolved Hide resolved
buildSrc/src/main/kotlin/BuildInfo.kt Outdated Show resolved Hide resolved
buildSrc/src/main/kotlin/BuildInfo.kt Outdated Show resolved Hide resolved
buildSrc/src/main/kotlin/pklJavaLibrary.gradle.kts Outdated Show resolved Hide resolved
buildSrc/src/main/kotlin/pklKotlinLibrary.gradle.kts Outdated Show resolved Hide resolved
settings.gradle.kts Outdated Show resolved Hide resolved
@sgammon sgammon requested a review from bioball January 22, 2025 06:26
@sgammon

This comment was marked as outdated.

@sgammon sgammon changed the title JDK 21+ toolchain JDK 21 toolchain Jan 22, 2025
@sgammon

This comment was marked as outdated.

@sgammon sgammon force-pushed the feat/jvm21plus-upstream branch from f9daed3 to 263588b Compare January 23, 2025 05:19
sgammon

This comment was marked as outdated.

settings.gradle.kts Outdated Show resolved Hide resolved
pkl-cli/pkl-cli.gradle.kts Outdated Show resolved Hide resolved
pkl-executor/pkl-executor.gradle.kts Outdated Show resolved Hide resolved
buildSrc/src/main/kotlin/pklJavaLibrary.gradle.kts Outdated Show resolved Hide resolved
buildSrc/src/main/kotlin/pklJavaLibrary.gradle.kts Outdated Show resolved Hide resolved
@sgammon
Copy link
Contributor Author

sgammon commented Jan 24, 2025

Some quick benchmarks as a sanity check. Not a huge improvement but no regressions either, which is what I was worried about. These are benchmarks using the native binary, compared with the 0.27.1 release.

Summary

Test Performance Change
Startup time Minor improvement
Evaluation time Minor improvement
Binary size Reduction by 12mb (~10%)
Memory usage Negligible improvement

Speed

pkl --help

Benchmark 1: 0.27.1
  Time (mean ± σ):       3.9 ms ±   0.4 ms    [User: 2.7 ms, System: 1.0 ms]
  Range (min … max):     3.4 ms …   7.2 ms    250 runs

  Warning: Statistical outliers were detected. Consider re-running this benchmark on a quiet system without any interferences from other programs. It might help to use the '--warmup' or '--prepare' options.

Benchmark 2: 0.28.x
  Time (mean ± σ):       3.8 ms ±   0.3 ms    [User: 2.8 ms, System: 0.9 ms]
  Range (min … max):     3.3 ms …   5.6 ms    250 runs

Summary
  0.28.x ran
    1.02 ± 0.13 times faster than 0.27.1

pkl analyze imports ./.config/config.pkl

Benchmark 1: 0.27.1
  Time (mean ± σ):      74.1 ms ±   3.6 ms    [User: 59.4 ms, System: 10.9 ms]
  Range (min … max):    68.8 ms …  84.4 ms    250 runs

Benchmark 2: 0.28.x
  Time (mean ± σ):      72.5 ms ±   3.1 ms    [User: 58.1 ms, System: 10.6 ms]
  Range (min … max):    67.7 ms …  80.7 ms    250 runs

Summary
  0.28.x ran
    1.02 ± 0.07 times faster than 0.27.1

pkl eval ./.circleci/config.pkl

Benchmark 1: 0.27.1
  Time (mean ± σ):      83.0 ms ±   4.2 ms    [User: 67.2 ms, System: 14.9 ms]
  Range (min … max):    75.7 ms …  95.1 ms    250 runs

Benchmark 2: 0.28.x
  Time (mean ± σ):      79.0 ms ±   2.7 ms    [User: 68.7 ms, System: 12.5 ms]
  Range (min … max):    74.8 ms …  86.3 ms    250 runs

Summary
  0.28.x ran
    1.05 ± 0.06 times faster than 0.27.1

Footprint

On disk

➜  pkl git:(feat/jvm21plus-upstream) du -h -L `which pkl` ./pkl-cli/build/executable/pkl-linux-amd64
116M    /home/linuxbrew/.linuxbrew/bin/pkl
104M    ./pkl-cli/build/executable/pkl-linux-amd64

In-memory

time -v pkl eval ./.circleci/config.pkl

  ...
        Maximum resident set size (kbytes): 124780 [124.78mb]
Full result at 0.27.1
        User time (seconds): 0.06
        System time (seconds): 0.00
        Percent of CPU this job got: 96%
        Elapsed (wall clock) time (h:mm:ss or m:ss): 0:00.08
        Average shared text size (kbytes): 0
        Average unshared data size (kbytes): 0
        Average stack size (kbytes): 0
        Average total size (kbytes): 0
        Maximum resident set size (kbytes): 124780
        Average resident set size (kbytes): 0
        Major (requiring I/O) page faults: 3
        Minor (reclaiming a frame) page faults: 16931
        Voluntary context switches: 28
        Involuntary context switches: 0
        Swaps: 0
        File system inputs: 520
        File system outputs: 0
        Socket messages sent: 0
        Socket messages received: 0
        Signals delivered: 0
        Page size (bytes): 4096
        Exit status: 0

time -v ./build/.../pkl-linux-amd64 eval ./.circleci/config.pkl

  ...
        Maximum resident set size (kbytes): 123680 [123.68mb]
Full result at 0.28.x
        User time (seconds): 0.07
        System time (seconds): 0.03
        Percent of CPU this job got: 105%
        Elapsed (wall clock) time (h:mm:ss or m:ss): 0:00.10
        Average shared text size (kbytes): 0
        Average unshared data size (kbytes): 0
        Average stack size (kbytes): 0
        Average total size (kbytes): 0
        Maximum resident set size (kbytes): 123680
        Average resident set size (kbytes): 0
        Major (requiring I/O) page faults: 109
        Minor (reclaiming a frame) page faults: 17246
        Voluntary context switches: 88
        Involuntary context switches: 0
        Swaps: 0
        File system inputs: 13016
        File system outputs: 0
        Socket messages sent: 0
        Socket messages received: 0
        Signals delivered: 0
        Page size (bytes): 4096
        Exit status: 0

Benchmarking setup

uname -a

Linux xxx 5.15.167.4-microsoft-standard-WSL2 #1 SMP Tue Nov 5 00:21:55 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux

Hardware

Processor	AMD Ryzen 9 9950X 16-Core Processor, 4300 Mhz, 16 Core(s), 32 Logical Processor(s)
Installed Physical Memory (RAM)	64.0 GB

@sgammon sgammon force-pushed the feat/jvm21plus-upstream branch from 8bfd605 to d087ea6 Compare January 24, 2025 01:25
@sgammon

This comment was marked as outdated.

buildSrc/src/main/kotlin/BuildInfo.kt Outdated Show resolved Hide resolved
buildSrc/src/main/kotlin/pklKotlinLibrary.gradle.kts Outdated Show resolved Hide resolved
docs/src/test/kotlin/DocSnippetTests.kt Show resolved Hide resolved
@bioball bioball modified the milestones: Pkl 0.27.1, Pkl 0.28.0 Jan 24, 2025
@sgammon sgammon force-pushed the feat/jvm21plus-upstream branch from 61b5e30 to 65d0ce3 Compare January 28, 2025 22:06
@sgammon
Copy link
Contributor Author

sgammon commented Jan 28, 2025

@bioball I've rebased this against latest main, and intend to finish cleanup and lockfiles shortly. That should make this PR ready for final review.

@bioball
Copy link
Contributor

bioball commented Jan 28, 2025

Sounds great!

@sgammon sgammon force-pushed the feat/jvm21plus-upstream branch 3 times, most recently from 8ad9dd5 to 465337a Compare January 29, 2025 00:05
@sgammon

This comment was marked as outdated.

@sgammon sgammon force-pushed the feat/jvm21plus-upstream branch from 465337a to 5f98585 Compare January 30, 2025 22:06
@sgammon

This comment was marked as outdated.

@sgammon sgammon force-pushed the feat/jvm21plus-upstream branch 2 times, most recently from 69750d3 to d6a8801 Compare January 31, 2025 03:18
buildSrc/src/main/kotlin/BuildInfo.kt Outdated Show resolved Hide resolved
pkl-commons/gradle.lockfile Outdated Show resolved Hide resolved
buildSrc/src/main/kotlin/BuildInfo.kt Outdated Show resolved Hide resolved
buildSrc/src/main/kotlin/BuildInfo.kt Show resolved Hide resolved
buildSrc/build.gradle.kts Outdated Show resolved Hide resolved
buildSrc/build.gradle.kts Outdated Show resolved Hide resolved
buildSrc/src/main/kotlin/BuildInfo.kt Outdated Show resolved Hide resolved
buildSrc/src/main/kotlin/BuildInfo.kt Outdated Show resolved Hide resolved
buildSrc/src/main/kotlin/BuildInfo.kt Outdated Show resolved Hide resolved
@sgammon sgammon force-pushed the feat/jvm21plus-upstream branch from d6a8801 to e85b259 Compare January 31, 2025 22:22
@sgammon

This comment was marked as resolved.

@sgammon sgammon force-pushed the feat/jvm21plus-upstream branch from e85b259 to 8d9c22c Compare February 2, 2025 00:21
Comment on lines +145 to +156
// Extra JPMS modules forced onto the module path via `--add-modules` in some cases.
private val jpmsAddModules = arrayOf("jdk.unsupported")

// Formats `jpmsExports` for use in JAR manifest attributes.
val jpmsExportsForJarManifest: String by lazy {
jpmsExports.joinToString(" ") { it.substringBefore("=") }
}

// Formats `jpmsExports` for use on the command line with `--add-exports`.
val jpmsExportsForAddExportsFlags: Collection<String> by lazy {
jpmsExports.map { "--add-exports=$it" }
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've decided to list the full suite of JPMS settings within BuildInfo, because it's much easier to use them throughout the codebase and avoid error-prone repetition; instead of rendering things in each project, jpmsExportsForJarManifest provides a JAR manifest value, and jpmsExportsForAddExportsFlags provides command-line flags.

This way, things are guaranteed to remain consistent across projects, and the actual set of JPMS exports can be made a private implementation detail.

Comment on lines +202 to +208
val jdkTestRange: Collection<JavaLanguageVersion> by lazy {
JavaVersionRange.inclusive(jdkTestFloor, jdkTestCeiling).filter { version ->
// unless we are instructed to test all JDKs, tests only include LTS releases and
// versions above the toolchain version.
testAllJdks || (JavaVersionRange.isLTS(version) || version >= jdkToolchainVersion)
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Filtering for JDK testing based on flags like testAllJdks now applies to all multi-JDK-related tasks, instead of just tests. Things like testStartJavaExecutable now respect these flags, where previously they did not.

Comment on lines +338 to +341
// In CI, this defaults to `true` to catch potential cross-JDK compat regressions or other bugs.
// In local dev, this defaults to `false` to speed up the build and reduce contributor load.
System.getProperty("pklMultiJdkTesting")?.toBoolean() ?: isCiBuild
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of disabling pklMultiJdkTesting by default, I've kept it enabled in CI, and disabled by default in local dev.

pkl-cli/gradle.lockfile Outdated Show resolved Hide resolved
Comment on lines +133 to +139
// Setup `testJavaExecutable` tasks for multi-JDK testing.
val testJavaExecutableOnOtherJdks =
if (buildInfo.multiJdkTesting) {
buildInfo.multiJdkTestingWith(testJavaExecutable)
} else {
emptyList()
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just like multi-JDK tests, testJavaExecutableOnJdk* and friends now only run as dependencies of check iff (we are in ci) OR (the local dev has activated multi-JDK testing).

Comment on lines +194 to +210
// 0.28 Preparing for JDK21 toolchains revealed that `testStartJavaExecutable` may pass, even though
// the evaluator fails. To catch this, we need to test the evaluator. We render the CircleCI config
// as a realistic test of the fat JAR.
val testEvalJavaExecutable by
setupJavaExecutableRun("testEvalJavaExecutable", evalTestFlags) { useRootDirAndSuppressOutput() }

// Run the same evaluator tests on all configured JDK test versions.
val testEvalJavaExecutableOnOtherJdks =
buildInfo.jdkTestRange.map { jdkTarget ->
setupJavaExecutableRun(
"testEvalJavaExecutableJdk${jdkTarget.asInt()}",
evalTestFlags,
serviceOf<JavaToolchainService>().launcherFor { languageVersion = jdkTarget },
) {
useRootDirAndSuppressOutput()
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These new tests effectively run:

jpkl eval ./.circleci/config.pkl > /dev/null

... to make sure that fat JARs run as expected end-to-end. This change is inspired by the testStartJavaExecutableJdk17 passing even though GraalVM 17 ultimately refused to load the evaluator.

Same as all other multi-JDK tests, jpkl eval ... will be run on the toolchain JDK only unless multi-JDK testing is active.

- feat: support for jvm21+ toolchain
- feat: support for gradle toolchains
- feat: pass `-PnativeArch=native` to build with `-march=native`
- test: multi-jdk testing support
- test: support for `jvm-test-suite` plugin
- test: add tasks to run `jpkl eval` on multiple jdks
- test: make jdk exec tests respect multi-jdk flags and ranges
- fix: remove mrjar classes at >jvm17 from fatjars
- fix: use jdk21 to run the tests (needed for `Unsafe.ensureInitialized`)
- fix: truffle svm dependency is required after graalvm `24.0.0`
- fix: warnings for gvm flag usage, renamed truffle svm macro
- fix: build with `--add-modules=jdk.unsupported` where needed
- fix: don't use `gu` tool for modern graalvm versions
- fix: catch `Throwable` instead of deprecated-for-removal `ThreadDeath`
- chore: buildinfo changes for JVM targets, toolchains
- chore: enforce testing at exactly jdk21
- chore: enforce build tooling at jdk21+
- chore: bump graalvm/truffle libs → `24.1.2`
- chore: toolchains for `buildSrc`

Signed-off-by: Sam Gammon <[email protected]>
- chore: update to jdk21 in circle jobs
- chore: re-gen circle jobs

Signed-off-by: Sam Gammon <[email protected]>
@sgammon sgammon force-pushed the feat/jvm21plus-upstream branch from 8d9c22c to a14ef41 Compare February 2, 2025 00:47
@sgammon sgammon requested a review from bioball February 2, 2025 00:48
@sgammon
Copy link
Contributor Author

sgammon commented Feb 2, 2025

@bioball This is ready for final review again :) I've left a self-review detailing the cleanups I've applied. Thank you for your patience while I figured out that GVM 17 bug.

@sgammon sgammon mentioned this pull request Feb 2, 2025
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.

3 participants