bcdev / jpy

A bi-directional Python-Java bridge used to embed Java in CPython or the other way round.
Apache License 2.0
187 stars 37 forks source link

Add search for JAVA_HOME based on "which java" #62

Open lukehutch opened 8 years ago

lukehutch commented 8 years ago

In my runtime environment, I can't rely on JAVA_HOME being set (or even necessarily determinable at compiletime), but would still like JVM detection to happen automatically. The following works for my environment:

from subprocess import check_output
java_bin = check_output(["which", "java"])
if java_bin:
  # Strip off the "/bin/java" suffix
  java_home = "/".join(java_bin.split("/")[:-2])

Can this be added as a fallback when the other JAVA_HOME mechanisms fail to find the DLL/.so?

Without this, at runtime I get an "undefined symbol: JNI_CreateJavaVM" error message, which is cryptic.

forman commented 8 years ago

Thanks for that! Note that you can also send pull requests via GitHub. A little modification may be required to make it generally applicable on Unix-based systems, e.g. it may not always be the "/bin/java" suffix.

Did you know that you can also set java_home in a jpyconf.py file?

lukehutch commented 8 years ago

Yes, I know about jpyconf.py and configuration via jpyutils.py. I was hoping for autodetection of the JRE location, since in my deployment environment, the JRE path is not easy to detect, except via the location of the java binary.

Unfortunately I got pulled off this project though, so I'm not working with jpy anymore. I didn't submit a patch, because I know that the code I suggested is not complete and is not portable. I know I really should submit a patch rather than suggestions, but unfortunately I just don't have the bandwidth for this, I was just hoping the suggestion would make jpy better.

One thing that is missing from the code, to make this generally applicable, is the following of symbolic links: on Fedora, "which java" returns /usr/bin/java , which is a soft link to /etc/alternatives/java , which is a soft link to something like /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.60-14.b27.fc22.x86_64/jre/bin/java . A soft-link follower would have to add paths into a set as it goes to prevent getting stuck in an infinite loop. (Or maybe Python has some builtin method for canonicalizing paths to an arbitrary link depth?)

Another thing missing is Windows compatibility. I used "/" rather than os.path.sep, and in Windows, "which" should be replaced with "where", although I don't know if that is universally available. It's possible that on Windows, some mechanism other than soft links is used to direct the java.exe file on %PATH% to the JRE (I honestly have no idea).

valgur commented 8 years ago

A portable option for JRE autodetection would be to do it within Python instead of using which/where. The following slightly modified version of http://stackoverflow.com/a/1322060 should work.

import os

def find_in_path(cmd):
    # can't search the path if a directory is specified
    assert not os.path.dirname(cmd)

    extensions = os.environ.get("PATHEXT", "").split(os.pathsep)
    for directory in os.environ.get("PATH", "").split(os.pathsep):
        base = os.path.join(directory, cmd)
        options = [base] + [(base + ext) for ext in extensions]
        for filename in options:
            if os.path.exists(filename) and os.path.isfile(filename):
                return filename
    return None
lukehutch commented 8 years ago

Looks like the right sort of approach. To find the JRE though, once you find the "java" binary on the system path, you need to call os.path.realpath(filename) to resolve symlinks, then check if the parent directory is a JRE installation. (I believe the correct test for the presence of a JRE root dir is to open lib/rt.jar, and look in the manifest file to verify that rt.jar is part of the Java platform.)

ShaheedHaque commented 5 years ago

If Python 3.3 or newer is acceptable, there is a shutil.which, which might provide the desired portability.

lukehutch commented 5 years ago

I'm no longer working on this (I was at Google at the time, and the runtime environment I was dealing with was Google's internal datacenter runtime environment). However, I wanted to add that the solution I suggested is incomplete -- you need to follow softlinks too:

$ ls -l /usr/bin/java
lrwxrwxrwx 1 root root 22 Dec  4 02:22 /usr/bin/java -> /etc/alternatives/java
$ ls -l /etc/alternatives/java
lrwxrwxrwx 1 root root 29 Dec  4 02:22 /etc/alternatives/java -> /usr/java/jdk-11.0.1/bin/java
forman commented 5 years ago

@ShaheedHaque Thanks! @lukehutch Thanks!

We'll give shutil.which a try.

ShaheedHaque commented 5 years ago

Actually, I have an alternative suggestion: use jshell and java.home like this:

echo 'System.out.println(System.getProperty("java.home"))' | jshell -

only wrapped in a call to subprocess.run() or similar. The point being that this gives direct access to whatever Java itself thinks JAVA_HOME is, and depending on the existence of jshell on the $PATH is, I think, no worse than looking for java itself. I'd be happy to propose a PR if that would be of assistance.