jshell version != JDK version
I've got everything in my project using Java17:
% echo $JAVA_HOME
/Library/Java/JavaVirtualMachines/amazon-corretto-17.jdk/Contents/Home/
% which java
/Library/Java/JavaVirtualMachines/amazon-corretto-17.jdk/Contents/Home/bin/java
% which jshell
/Library/Java/JavaVirtualMachines/amazon-corretto-17.jdk/Contents/Home/bin/jshell
Similarly, my build.gradle specifies Java17 in all the right places and if I call System.getProperty("java.version"), it prints "17.0.1".
But, when I run ./gradlew --console plain jshell, I get this:
...
> Task :jshell
| Welcome to JShell -- Version 11.0.7
| For an introduction type: /help intro
jshell>
Is this configurable somewhere?
That's weird! What you get executing ./gradlew --version?
% ./gradlew --version
------------------------------------------------------------
Gradle 7.3.3
------------------------------------------------------------
Build time: 2021-12-22 12:37:54 UTC
Revision: 6f556c80f945dc54b50e0be633da6c62dbe8dc71
Kotlin: 1.5.31
Groovy: 3.0.9
Ant: Apache Ant(TM) version 1.10.11 compiled on July 10 2021
JVM: 17.0.1 (Amazon.com Inc. 17.0.1+12-LTS)
OS: Mac OS X 12.0.1 x86_64
I do have Java11 installed separately on my machine, but at least my environment for this project should be entirely Java17, and I've got hopefully all the right things set in build.gradle:
allprojects {
...
tasks.withType(JavaCompile).configureEach {
...
sourceCompatibility = '17'
targetCompatibility = '17'
}
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
sourceCompatibility = JavaLanguageVersion.of(17)
targetCompatibility = JavaLanguageVersion.of(17)
}
}
What the plugin basically does is to invoke the command with a prebuild set of arguments, see: https://github.com/mrsarm/jshell-plugin/blob/2b3b04cac217591ff649cf45a0a625a5ba36fc49/src/main/groovy/com/github/mrsarm/jshell/plugin/JShellPlugin.groovy#L101-L109
So, for some reason the jshell command in your path is the one from Java 11, although I see you checked that with which jshell :thinking: . Did you try to execute just jshell in the same console to see what is the output? In my case if I enable Java 17:
$ jshell
| Welcome to JShell -- Version 17.0.1
| For an introduction type: /help intro
jshell>
Yeah, if I run the commandline jshell, I get Java17.
I'm thinking that maybe this is an old Gradle process that's caching old environment variables.
Nonetheless, you really do want to launch a jshell that matches whatever Java version is used for compiling and running the code, as opposed to whatever is in the search path first.
Good point ! in the past changing the JAVA_HOME variable allowed to change the JShell by the one in that Java version pointed, but it's not the case any more:
$ java -version
openjdk version "11.0.12" 2021-07-20
...
$ JAVA_HOME="$HOME/.sdkman/candidates/java/17.0.1-open" gradle --console plain jshell
...
...
| Welcome to JShell -- Version 11.0.12
| For an introduction type: /help intro
jshell>
The reason is because in this commit https://github.com/mrsarm/jshell-plugin/commit/b6aebbbea22d83a377fa22bf8bb07373fae54cd3 we changed the way we call JShell, executing a shell process instead of using the JShell API from Java. The reason was that the API stopped to work in Java 12 and above, not sure why.
So this section explaining how to use the $JAVA_HOME is not valid any more, and the plugin should provide a way to "patch" this issue. I'll create a ticket later to keep track of the changes needed.
Back to your problem, that it's in part due the problems described above, and as you pointed out:
I'm thinking that maybe this is an old Gradle process that's caching old environment variables.
Try also execute Gradle with the --no-daemon flag to see what happens.
Sure enough, killing and restarting the Gradle daemon made it (finally) do the right thing.
Meanwhile, when I do this, I don't get tab-based autocomplete on my code (e.g., import verylongpackagename.verylongclassname and hitting very). My guess is that Gradle isn't passing the individual characters through, but is instead doing line buffering. Any idea how to fix this?
Good to know that !
About auto-completion, unfortunately that is something that doesn't work due Gradle limitations, checkout https://github.com/mrsarm/jshell-plugin#tab-completion-not-working.
What I do suggest, also as pointed out in the section above, it's to call Gradle with rlwrap to at least have arrows key commands working (e.g. press arrow-up to autocomplete with previous commands executed):
$ rlwrap ./gradlew --console plain jshell
Hmm. Feature request: a new Gradle action, "printJShellCommand" or similar, which dumps something you can cut-and-paste, then run on your own.
(Use case: doing a live-coding demo with JShell, where autocomplete keeps you from making a mess of the screen.)
Yea , I was thinking about adding something like that, maybe instead of an alternative command, as an argument, because JShell currently supports another optional argument and I may add others in the future.
It can be something like:
$ ./gradlew jshell -Pjshell.printcmd
The only drawback is that the output command can be really long, here is an example of how it would looks like in a Spring app I have that actually doesn't have more dependencies other than Spring and spring-ctx (Spring has a lot of dependencies, same for any modern framework):
jshell --startup DEFAULT --startup PRINTING --class-path /projects/mrsarm/java/jshell-plugin/samples/sample/gs-rest-service/complete/build/classes/java/main:/home/user/.gradle/caches/modules-2/files-2.1/com.github.mrsarm/spring-ctx/1.0.0/4f66913e290268028eaa4a4da884c59873cc95df/spring-ctx-1.0.0.jar:/home/user/.gradle/caches/modules-2/files-2.1/org.springframework/spring-webmvc/5.3.4/8dd52fbe8eafcc7c80998087ec6635baf7a98c20/spring-webmvc-5.3.4.jar:/home/user/.gradle/caches/modules-2/files-2.1/org.springframework/spring-web/5.3.4/d93829e24a50ed22e781f2302680a210cac5ee84/spring-web-5.3.4.jar:/home/user/.gradle/caches/modules-2/files-2.1/org.springframework/spring-context-support/5.3.4/a57000f628b50bd8eb57719aedb32c5330a4c8b4/spring-context-support-5.3.4.jar:/home/user/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.datatype/jackson-datatype-jdk8/2.11.4/e1540dea3c6c681ea4e335a960f730861ee3bedb/jackson-datatype-jdk8-2.11.4.jar:/home/user/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.datatype/jackson-datatype-jsr310/2.11.4/ce6fc76bba06623720e5a9308386b6ae74753f4d/jackson-datatype-jsr310-2.11.4.jar:/home/user/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.module/jackson-module-parameter-names/2.11.4/432e050d79f2282a66c320375d628f1b0842cb12/jackson-module-parameter-names-2.11.4.jar:/home/user/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.core/jackson-databind/2.11.4/5d9f3d441f99d721b957e3497f0a6465c764fad4/jackson-databind-2.11.4.jar:/home/user/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-autoconfigure/2.4.3/2f8e1e682e55f70c8ea557982c7adb1256f41bcf/spring-boot-autoconfigure-2.4.3.jar:/home/user/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot/2.4.3/de2bd17a8eb9bc3dfa629aa06f2e9fe3bf603c85/spring-boot-2.4.3.jar:/home/user/.gradle/caches/modules-2/files-2.1/jakarta.annotation/jakarta.annotation-api/1.3.5/59eb84ee0d616332ff44aba065f3888cf002cd2d/jakarta.annotation-api-1.3.5.jar:/home/user/.gradle/caches/modules-2/files-2.1/org.springframework/spring-context/5.3.4/fbeadeb0e4d272599a938ec345e99e5f9a76e919/spring-context-5.3.4.jar:/home/user/.gradle/caches/modules-2/files-2.1/org.springframework/spring-aop/5.3.4/b7180a6427ab524bc1cbd31bf38dd2182632515f/spring-aop-5.3.4.jar:/home/user/.gradle/caches/modules-2/files-2.1/org.springframework/spring-beans/5.3.4/ac6c5ea0ba82f555405f74104cf378f8071c6d25/spring-beans-5.3.4.jar:/home/user/.gradle/caches/modules-2/files-2.1/org.springframework/spring-expression/5.3.4/42b71fa955e43a86471509aef14cefe756fc3794/spring-expression-5.3.4.jar:/home/user/.gradle/caches/modules-2/files-2.1/org.springframework/spring-core/5.3.4/46c1f8abd9e02a292c42a257350f365cec152b5d/spring-core-5.3.4.jar:/home/user/.gradle/caches/modules-2/files-2.1/org.yaml/snakeyaml/1.27/359d62567480b07a679dc643f82fc926b100eed5/snakeyaml-1.27.jar:/home/user/.gradle/caches/modules-2/files-2.1/org.apache.tomcat.embed/tomcat-embed-websocket/9.0.43/78232648dde1a5fd5093f0a3999e593d208e2eab/tomcat-embed-websocket-9.0.43.jar:/home/user/.gradle/caches/modules-2/files-2.1/org.apache.tomcat.embed/tomcat-embed-core/9.0.43/1d102277426bdd5b12f048731a91665bb69347d1/tomcat-embed-core-9.0.43.jar:/home/user/.gradle/caches/modules-2/files-2.1/org.glassfish/jakarta.el/3.0.3/dab46ee1ee23f7197c13d7c40fce14817c9017df/jakarta.el-3.0.3.jar:/home/user/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.core/jackson-annotations/2.11.4/2c3f5c079330f3a01726686a078979420f547ae4/jackson-annotations-2.11.4.jar:/home/user/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.core/jackson-core/2.11.4/593f7b18bab07a76767f181e2a2336135ce82cc4/jackson-core-2.11.4.jar:/home/user/.gradle/caches/modules-2/files-2.1/ch.qos.logback/logback-classic/1.2.3/7c4f3c474fb2c041d8028740440937705ebb473a/logback-classic-1.2.3.jar:/home/user/.gradle/caches/modules-2/files-2.1/org.apache.logging.log4j/log4j-to-slf4j/2.13.3/966f6fd1af4959d6b12bfa880121d4a2b164f857/log4j-to-slf4j-2.13.3.jar:/home/user/.gradle/caches/modules-2/files-2.1/org.slf4j/jul-to-slf4j/1.7.30/d58bebff8cbf70ff52b59208586095f467656c30/jul-to-slf4j-1.7.30.jar:/home/user/.gradle/caches/modules-2/files-2.1/org.springframework/spring-jcl/5.3.4/ca012eb4e9e57f767aa9d5e51fe9a09b28140808/spring-jcl-5.3.4.jar:/home/user/.gradle/caches/modules-2/files-2.1/ch.qos.logback/logback-core/1.2.3/864344400c3d4d92dfeb0a305dc87d953677c03c/logback-core-1.2.3.jar:/home/user/.gradle/caches/modules-2/files-2.1/org.slf4j/slf4j-api/1.7.30/b5a4b6d16ab13e34a88fae84c35cd5d68cac922c/slf4j-api-1.7.30.jar:/home/user/.gradle/caches/modules-2/files-2.1/org.apache.logging.log4j/log4j-api/2.13.3/ec1508160b93d274b1add34419b897bae84c6ca9/log4j-api-2.13.3.jar
The command above has exactly 4708 characters ! Moreover, if you pipe the output in a bash file (./gradlew jshell -Pjshell.printcmd > jshell.sh) to then just execute the bash script, it may lead to outdated scripts: as soon you update a dependency the bash will be outdated and you won't notice it. Same for compilation: triggering jshell from Gradle allows the plugin to execute the compilation first (although not always works).
So what I have in mind, but it can take a while to develop, it's actually add a flag to create a script called jshellw (similar to gradlew :smile: ), that would allow to call first the compilation task, then the jshell plugin to produce an output like the above in an output stream, and then execute that output stream in a bash session within the current session (something like ..jshell -Pjshell.printcmd | bash), so it would be always guaranteed the compilation, and that the call to JShell is up to date, auto-completion will work, and would be even easier to run jshell.
But for now I'm short of time to work on it :disappointed:
BTW, if you want to "build" a script like the above with JShell, you can get the list of all the dependencies ready for copy & paste executing the plugin with the --info flag:
$ ./gradlew --console plain jshell --info
You will see more logs than usual while Gradle and the plugin start. Look for :jshell executing with --class-path in the output, you will see all the dependencies, separated by the : char as Java requires, so then copy and paste in another terminal:
$ jshell --startup DEFAULT --startup PRINTING --class-path DEPENDENCIES
(--startup DEFAULT --startup PRINTING is optional but useful).
Plus, if you want to execute a .jsh script at startup, append --startup SCRIPT.jsh.
I'm a fan of the jshellw idea, or at least having a Gradle action that writes out a bespoke jshell command that gets you out of the limitations of Gradle's input buffering.
(Apparently the extracted shell command that I want, for my own project, is 8260 characters long. Sheesh.)