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

jshell version != JDK version #12

Open
danwallach opened this issue Feb 10, 2022 · 13 comments
Open

jshell version != JDK version #12

danwallach opened this issue Feb 10, 2022 · 13 comments

Comments

@danwallach
Copy link

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?

@mrsarm
Copy link
Owner

mrsarm commented Feb 10, 2022

That's weird! What you get executing ./gradlew --version?

@danwallach
Copy link
Author

% ./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

@danwallach
Copy link
Author

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)
    }
}

@mrsarm
Copy link
Owner

mrsarm commented Feb 10, 2022

What the plugin basically does is to invoke the command with a prebuild set of arguments, see:

project.tasks.register("jshell", Exec) {
group = 'application'
description = 'Runs a JShell session with all the code and dependencies.'
dependsOn jshellSetupTask
doFirst {
standardInput = System.in
executable = "jshell"
args(shellArgs)
}

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 🤔 . 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>

@danwallach
Copy link
Author

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.

@mrsarm
Copy link
Owner

mrsarm commented Feb 10, 2022

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 b6aebbb 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.

@danwallach
Copy link
Author

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 after 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?

@mrsarm
Copy link
Owner

mrsarm commented Feb 10, 2022

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

@danwallach
Copy link
Author

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.)

@mrsarm
Copy link
Owner

mrsarm commented Feb 10, 2022

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 😄 ), 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 😞

@mrsarm
Copy link
Owner

mrsarm commented Feb 10, 2022

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.

@danwallach
Copy link
Author

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.

@danwallach
Copy link
Author

(Apparently the extracted shell command that I want, for my own project, is 8260 characters long. Sheesh.)

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

No branches or pull requests

2 participants