Auto-detecting Java installations

Some applications need to discover Java installations.

NixOS already populates JAVA_HOME and adds java/javac to the PATH, which is a great start.

However, some applications want to discover not just the ‘current’ Java, but all installed/available JRE/JDK’s. Unfortunately, there’s no nice convention on how to make their JAVA_HOME’s discoverable. The result of this is that applications are resorting to terrible hacks

In the long run, I would like those terrible hacks to also discover JDK’s installed on NixOS.

Perhaps we should think about providing a not-too-terrible way to discover JRE/JDK’s in NixOS, and then add support for that approach to those tools?

One way I could imagine would be to introduce a JAVA_HOMES environment variable to hold a colon-separated list, and have each JDK package contribute its JAVA_HOME to that list (just like it contributes its bin directory to the PATH). Would that make sense?

1 Like

Why do you want to have multiple JDK’s available at the same time?

Things like:

  • Build on JDK8, but test that the resulting artifact also works on JDK11
  • Build on JDK11 but target JDK8, and also test the artifacts indeed work with JDK8
  • Build part of your artifact targeting JDK8, but another part targeting JDK11

I agree these are ‘niche’ use cases, and we shouldn’t support them when this comes at a prohibitive cost. But exposing them through a JAVA_HOMES environment variable seems fairly non-intrusive, and would be rather useful at least to me ;).

As a workaround to make this work with sbt for now I have in my shell.nix:

{ pkgs ? import <nixpkgs> {} }:
pkgs.mkShell {
  ...
  JAVA_8_HOME = "${pkgs.jdk}/lib/openjdk";
  JAVA_11_HOME = "${pkgs.jdk11}/lib/openjdk";
}

And then in an .sbt file:

fullJavaHomes in ThisBuild := fullJavaHomes.value + (("1.8", file(sys.env("JAVA_8_HOME")))) + (("11", file(sys.env("JAVA_11_HOME"))))

Not ideal, but gets the job done. I would still love to see a nice convention so we can solve this ‘upstream’ (in nix/sbt) instead of for each local build.

1 Like

Gradle 6.7 introduced what they call “Toolchains” (also see the docs). While it allows to automatically download specified JDKs (which is not what I expect Nix users to want), it also “auto-detects” JDKs present on the system. @raboof would probably call most of these detection mechansims “dirty hacks” :stuck_out_tongue:

They expose two properties to register JDKs: org.gradle.java.installations.{fromEnv,paths} with slightly different semantics. Please consult the docs referenced above.

@raboof could use o.g.j.i.fromEnv=JAVA_8_HOME,JAVA_11_HOME.

I thought about adding an argument to gradleGen so that we could pass e.g. a list of JDKs that should be “forwarded” to Gradle. Sadly I couldn’t find a way to make that happen, because AFAIK the properties mentioned above cannot be set via environment variables (they are not in org.gradle.project.*) and I couldn’t make passing via GRADLE_OPTS work.

Then I thought, that maybe we could implement yet another detection mechanism, directly in Gradle, and ended up here. Any progress or new findings with regards to auto-detecting JDKs on top of Nix?

1 Like

I would - though to be clear there’s nothing wrong with using dirty hacks when no more reliable mechanism is available - it just indicates there’s a need for a more reliable mechanism :wink:

Not really. I made an incomplete prototype of providing JAVA_x_HOME environment variables (#76699), but since I received no encouraging feedback I abandoned that.

I still like that approach (or at least I don’t see any particularly viable alternative). The main criticism seems to be it’s no “generally accepted standard”, which is fair enough - so perhaps the next step is to lobby for it to become a “generally accepted standard” outside of NixOS first :slight_smile: .

2 Likes

I would love to see (have) auto-detection of JDK(s) in Gradle working also on NixOS.

Although, it’s possible to solve it via method mentioned before, it’s still a custom solution / workaround, than needs to be done manually.

However, I don’t think the solution in the PR is the best design, because it sets single environment variable per major JDK version. And, it is possible to install multiple different JDKs for the same major version. Gradle should be able to pick them all, and then select appropriate one for the project.

Gradle being able to pick up all JDKs from environment variables in a standard way - without a need to manually set custom gradle properties (and env-vars, too) - is an useful standard, IMO.

I would opt for something like this. It can store an arbitrary number of (different) JDKs.

Trying to come up with a standard, that would differentiate JDKs from each other would be a complicated thing. And, IMO, doesn’t add much value - Gradle can inspect and choose a correct JDK on its own - it needs only paths to be able to locate them.

This might not be the solution that satisfies @kravemir’s requirements, but I’d like to note that #119444 landed in the meantime, which allows you to pass Toolchains into the Gradle derivation. See pkgs/development/tools/build-managers/gradle/default.nix:43-45.

1 Like

Or, maybe instead of trying to bend Gradle to support NixOS.

The NixOS could allow to read-only mount (multiple) JVM packages (derivations) to one of standard locations:

/usr/lib/jvm
/usr/lib64/jvm
/usr/java
/usr/local/java
/opt/java

Similarly, as in other distributions, it’s sym-linked vague to more specific, somehow like (don’t remember now details):

java17                                     -> java17-openjdk17
java17-openjdk17                           -> java17-openjdk17-very-specific-version
java17-openjdk17-very-specific-version        (actual contents)
java17-oraclejdk17                         -> java17-oraclejdk17-very-specific-version
java17-oraclejdk17-very-specific-version      (actual contents)

This way, I could setup IDE to always use java17-openjdk17 and don’t care about specific version, but make sure it’s not Oracle JDK (or else I don’t want for given project) .

I doubt that this would make it into NixOS. If you have an application that you cannot patch, look into pkgs.buildFHSEnv. Gradle also registers the Java runtime that it was invoked with as a toolchain. So I’d think that just installing pkgs.jdk17 might already be sufficient. You can also just download a JDK and point Gradle at it.

@kravemir @raboof please provide feedback for my new attempt at java: Populate `/share/jvm` by lorenzleutgeb · Pull Request #312887 · NixOS/nixpkgs · GitHub