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

--jvm tries to find Java executable system-wide. #11500

Merged
merged 27 commits into from
Nov 18, 2024

Conversation

Akirathan
Copy link
Member

@Akirathan Akirathan commented Nov 6, 2024

Fixes #11274

Pull Request Description

Fixes --jvm option, given to the native image. This was failing on my machine, because when given --jvm option, the runner was trying to find the java executable from the distribution manager's runtime (on my system located in ~/.local/share/enso/runtime) and it used the first runtime found. But the first runtime on my system is JDK 17.

The --jvm option now tries to:

  • Find a JDK from the distribution manager that has the same version as the JDK used for building the engine.
    • If there is not an exact version match, it tries to find a runtime from distribution manager that is newer.
    • If none, fallback to system-wide search
  • System-wide search tries to find java from $JAVA_HOME and from $PATH. But this is just a fallback.

Important Notes

  • Added test to Engine CI jobs that pass --jvm argument to a native image of engine-runner
  • runtime-version-manager sbt project migrated to a JPMS module
  • engine-runner now depends on runtime-version-manager.
  • Removed unnecessary stuff in runtime-version-manager dealing with outdated gu Graal Updater utility.
  • Extracted GraalVersionManager from RuntimeVersionManager

Checklist

Please ensure that the following checklist has been satisfied before submitting the PR:

  • The documentation has been updated, if necessary.
  • Screenshots/screencasts have been attached, if there are any visual changes. For interactive or animated visual changes, a screencast is preferred.
  • All code follows the
    Scala,
    Java,
    TypeScript,
    and
    Rust
    style guides. In case you are using a language not listed above, follow the Rust style guide.
  • Unit tests have been written where possible.
  • If meaningful changes were made to logic or tests affecting Enso Cloud integration in the libraries,
    or the Snowflake database integration, a run of the Extra Tests has been scheduled.
    • If applicable, it is suggested to paste a link to a successful run of the Extra Tests.

@Akirathan Akirathan added the CI: No changelog needed Do not require a changelog entry for this PR. label Nov 6, 2024
@Akirathan Akirathan self-assigned this Nov 6, 2024
Copy link
Member

@JaroslavTulach JaroslavTulach left a comment

Choose a reason for hiding this comment

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

The --jvm option now tries to find java executable from JAVA_HOME and from PATH. If that is not found, the distribution manager's runtimes are used as a fallback.

I'd say that this is the wrong order. The target audience of Enso users is unlikely to have proper JVM installed in the system. Searching for a system JVM is good for us, developers, but not for our target audience.

trying to find the java executable from the distribution manager's runtime (on my system located in ~/.local/share/enso/runtime) and it used the first runtime found. But the first runtime on my system is JDK 17.

It is good we have such a failing example! What is the layout of your Enso JVM runtimes? That is an important information for your #11274 bug report.

Anyway, the enso launcher should:

  • search managed runtimes (in ~/.local/share/enso/runtime) first
  • ignore the outdated ones - e.g. having JVM spec version lower than 21 (right now)
  • select the exact match, if found
  • select acceptable match (e.g. newer JDK), if found - probably with some warning printed
  • if no match found among managed runtimes, fallback to system JVM - with some warning printed, if the JDK isn't exactly the one we expect

Would the above steps work for your system setup, Pavel?

@Akirathan
Copy link
Member Author

Searching for a system JVM is good for us, developers, but not for our target audience.

That is a good point.

It is good we have such a failing example! What is the layout of your Enso JVM runtimes? That is an important information for your #11274 bug report.

$ ls -l ~/.local/share/enso/runtime
drwxrwxr-x 1 pavel pavel 612 Nov  1  2023 graalvm-ce-java17.0.7-23.0.0
drwxrwxr-x 1 pavel pavel 114 Nov 28  2023 graalvm-ce-java21.0.1-23.1.0
drwxrwxr-x 1 pavel pavel 114 Feb 29  2024 graalvm-ce-java21.0.2-23.1.2
drwxrwxr-x 1 pavel pavel 114 Apr 16  2024 graalvm-ce-java21.0.2-24.0.0

Would the above steps work for your system setup, Pavel?

Certainly. Sounds like a good plan. I will see what I can do.

@JaroslavTulach
Copy link
Member

$ ls -l ~/.local/share/enso/runtime
drwxrwxr-x 1 pavel pavel 612 Nov  1  2023 graalvm-ce-java17.0.7-23.0.0
drwxrwxr-x 1 pavel pavel 114 Nov 28  2023 graalvm-ce-java21.0.1-23.1.0
drwxrwxr-x 1 pavel pavel 114 Feb 29  2024 graalvm-ce-java21.0.2-23.1.2
drwxrwxr-x 1 pavel pavel 114 Apr 16  2024 graalvm-ce-java21.0.2-24.0.0

It would be great, if we selected the same JDK as used for the build (however that's graalvm-community-openjdk-21.0.2+13.1 and I don't see it) and then try any JDK21 (probably the highest - e.g. graalvm-ce-java21.0.2-24.0.0) maybe with a warning...

Sounds like a good plan. I will see what I can do.

DistributionManager shall do most of the selection logic. CCing @radeusgd to give us an expert guidance.

Comment on lines 13 to 36
/**
* Tries to find {@code java} executable on the system. If a system-wide JDK is not found, tries
* to find it in the {@link DistributionManager distribution} runtimes.
*
* @return null if cannot be found. Otherwise, returns the absolute path to the executable, or
* simply {@code java} if it is on the {@code PATH}.
*/
static String findJavaExecutable() {
var javaHome = System.getenv("JAVA_HOME");
if (javaHome != null) {
var java = new File(javaHome, "bin/java").getAbsoluteFile();
if (java.exists()) {
return java.getAbsolutePath();
}
}
if (isJavaOnPath()) {
return "java";
}
var javaInRuntime = findJavaExecutableInDistributionRuntimes();
if (javaInRuntime != null) {
return javaInRuntime.toAbsolutePath().toString();
}
return null;
}
Copy link
Member

Choose a reason for hiding this comment

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

I think the precedence should actually look into distribution first and only afterwards use system JVM.

The whole purpose of ensoup was to manage JVMs related to the engine versions, so that the engine is run with the right compatible JVM and not a random version that may be incompatible with it (I guess it used to be more important before Truffle 'got unchained').

I think ideally we should first to run the JVM version required by the engine if it's found in the distribution. Then we can fallback to using the logic of searching JAVA_HOME and Path.

Ideally I'd also check the JVM version and log a warning if the version required by engine does not match the version that has been found.

Copy link
Member

@radeusgd radeusgd Nov 7, 2024

Choose a reason for hiding this comment

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

JVMs related to the engine versions

This version is stored in the engine's manifest - see for example built-distribution/enso-engine-0.0.0-dev-windows-amd64/enso-0.0.0-dev/manifest.yaml.

It contains fields: graal-vm-version and graal-java-version.
You can see lib/scala/runtime-version-manager/src/main/scala/org/enso/runtimeversionmanager/components/Manifest.scala for the logic of loading the manifest and extracting the GraalVMVersion from it.

We have a GraalCEReleaseProvider which translates this GraalVMVersion into a strategy for downloading it from GitHub - but you probably don't need this.

The function graalDirectoryForVersion in RuntimeVersionManager takes GraalVMVersion and gives you back the directory name under which this JVM version will be sitting in the $ENSO_DATA_DIRECTORY/runtime (installed by ensoup).

I guess you could factor out the Manifest and graalDirectoryForVersion utilities to a common package and you could use it to figure out the JVM version tied to the engine.


Btw. the manifest also contains a jvm-options field. I'd be very happy if we could keep the (sometimes changing) options in a single place.

Copy link
Member

Choose a reason for hiding this comment

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

I guess if you want to check if the JVM version is correct you need to call java --version and somehow parse its output and compare with graal-java-version.

e.g. I get

openjdk 21.0.2 2024-01-16
OpenJDK Runtime Environment GraalVM CE 21.0.2+13.1 (build 21.0.2+13-jvmci-23.1-b30)
OpenJDK 64-Bit Server VM GraalVM CE 21.0.2+13.1 (build 21.0.2+13-jvmci-23.1-b30, mixed mode, sharing)

I guess I want to extract the second word from the first line. At least for now that seems to work. But we can update the logic whenever the version format of expected JVM changes - unexpected format just means it's not the JVM we expect :)

Copy link
Member

Choose a reason for hiding this comment

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

Possibly better way than parsing output of java -version is to run a small program on the JVM:

public static void main(String... args) {
  System.out.prinltn(System.getProperty("java.vm.version"));
  // etc.
}

and just read the output line by line without the need to parse words.

Copy link
Member

Choose a reason for hiding this comment

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

Cool - that indeed sounds better, I didn't think of that

@Akirathan
Copy link
Member Author

As of 586fc73:

  • I have extracted GraalVersionManager from RuntimeVersionManager
    • Among other things, GraalVersionManager is able to list, and sort, all the installed runtimes in the distribution (in my case, it is inside ~/.local/share/enso/runtime)
  • engine-runner depends on runtime-version-manager
    • For that, I had to convert runtime-version-manager project to JPMS module.
  • Removed unnecessary stuff in runtime-version-manager dealing with outdated gu Graal Updater utility.
  • JavaFinder knows the exact version that was used for building -
    • And tries to find the runtime from the distribution with the exact version, or newer version

Only when there is no newer runtime found in the managed distribution runtimes, JAVA_HOME and system java is tried.

@Akirathan Akirathan added the CI: Clean build required CI runners will be cleaned before and after this PR is built. label Nov 8, 2024
Copy link
Member

@radeusgd radeusgd left a comment

Choose a reason for hiding this comment

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

Looks great!

@Akirathan
Copy link
Member Author

Akirathan commented Nov 14, 2024

Windows CI does not have java.exe on PATH - https://github.com/enso-org/enso/actions/runs/11840014687/job/32992849858?pr=11500#step:7:2539 . Removing this test from Windows again in 049b991

GitHub
Enso Analytics is a self-service data prep and analysis platform designed for data teams. - --jvm tries to find Java executable system-wide. · fb25d57

@radeusgd
Copy link
Member

Windows CI does not have java.exe on PATH - enso-org/enso/actions/runs/11840014687/job/32992849858?pr=11500#step:7:2539 . Removing this test from Windows again in 049b991

GitHub**--jvm tries to find Java executable system-wide. · enso-org/enso@fb25d57**Enso Analytics is a self-service data prep and analysis platform designed for data teams. - --jvm tries to find Java executable system-wide. · fb25d57

What about JAVA_HOME/bin?

@Akirathan Akirathan added the CI: Ready to merge This PR is eligible for automatic merge label Nov 18, 2024
Copy link
Collaborator

@hubertp hubertp left a comment

Choose a reason for hiding this comment

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

I'm a bit surprised this fails Windows PR check. What's the reason?

logger.warn("Falling back to java on PATH: {}", javaExe);
return javaExe;
}
logger.warn("No JDK found on PATH. Cannot start the runtime.");
Copy link
Collaborator

Choose a reason for hiding this comment

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

Shouldn't you be checking ENSO_JVM_PATH as well?

return null;
}

private static boolean isOnWindows() {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could just use OS.isWindows() instead? It's already a dependency, right?

try {
ProcessBuilder processBuilder;
if (isOnWindows()) {
processBuilder = new ProcessBuilder("java.exe", "-h");
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit:

Suggested change
processBuilder = new ProcessBuilder("java.exe", "-h");
processBuilder = new ProcessBuilder("java.exe", "-v");

if (isOnWindows()) {
processBuilder = new ProcessBuilder("java.exe", "-h");
} else {
processBuilder = new ProcessBuilder("java", "-h");
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
processBuilder = new ProcessBuilder("java", "-h");
processBuilder = new ProcessBuilder("java", "-v");

@mergify mergify bot merged commit 9a49a02 into develop Nov 18, 2024
42 checks passed
@mergify mergify bot deleted the wip/akirathan/11274-jvm-cmdlineopt-ni branch November 18, 2024 22:44
@JaroslavTulach
Copy link
Member

Congratulation to getting thru CI checks.

somebody1234 pushed a commit that referenced this pull request Nov 21, 2024
Fixes `--jvm` option, given to the native image. This was failing on my machine, because when given `--jvm` option, the runner was trying to find the `java` executable from the distribution manager's runtime (on my system located in `~/.local/share/enso/runtime`) and it used the first runtime found. But the first runtime on my system is JDK 17.

The `--jvm` option now tries to:
- Find a JDK from the distribution manager that has the same version as the JDK used for building the engine.
- If there is not an exact version match, it tries to find a runtime from distribution manager that is *newer*.
- If none, fallback to system-wide search
- System-wide search tries to find `java` from `$JAVA_HOME` and from `$PATH`. But this is just a fallback.

# Important Notes
- Added test to Engine CI jobs that pass `--jvm` argument to a native image of engine-runner
- ea3af5f
- `runtime-version-manager` sbt project migrated to a JPMS module
- `engine-runner` now depends on `runtime-version-manager`.
- Removed unnecessary stuff in `runtime-version-manager` dealing with outdated `gu` Graal Updater utility.
- Extracted [GraalVersionManager](https://github.com/enso-org/enso/blob/1455b025cb8e5141409ea96b75577e8884265a70/lib/scala/runtime-version-manager/src/main/java/org/enso/runtimeversionmanager/components/GraalVersionManager.java) from [RuntimeVersionManager](https://github.com/enso-org/enso/blob/d2e8994700fd36228f7873f040668013e643190a/lib/scala/runtime-version-manager/src/main/scala/org/enso/runtimeversionmanager/components/RuntimeVersionManager.scala)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CI: Clean build required CI runners will be cleaned before and after this PR is built. CI: No changelog needed Do not require a changelog entry for this PR. CI: Ready to merge This PR is eligible for automatic merge
Projects
None yet
Development

Successfully merging this pull request may close these issues.

enso --jvm fails with InvalidModuleDescriptorException
4 participants