Skip to content

Commit

Permalink
Improved java detection
Browse files Browse the repository at this point in the history
  • Loading branch information
jjlauer committed Nov 3, 2023
1 parent 8d87ba9 commit 9eb2207
Show file tree
Hide file tree
Showing 29 changed files with 356 additions and 33 deletions.
7 changes: 7 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -74,5 +74,12 @@
<scope>test</scope>
</dependency>

<dependency>
<groupId>com.fizzed</groupId>
<artifactId>crux-util</artifactId>
<version>1.0.43</version>
<scope>test</scope>
</dependency>

</dependencies>
</project>
4 changes: 2 additions & 2 deletions src/main/java/com/fizzed/jne/HardwareArchitecture.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ public enum HardwareArchitecture {
X32("x32", new String[] { "i386", "i586", "i686" }, new String[] { "x86" }),
X64("x64", new String[] { "x86_64", "amd64" }), // also known as amd64 or x86_64
ARMEL("armel", null, new String[] { "arm32v5", "arm32v6" }), // ARMEL, ARM 32-bit SF v6, v7, v5
ARMHF("armhf", null, new String[] { "arm32v7" }), // ARMHF stands for "ARM hard float", and is the name given to a Debian port for ARM processors (armv7+) that have hardware floating point support, which is found on most modern 32-bit ARM boards
ARMHF("armhf", null, new String[] { "arm32v7", "armv7l" }), // ARMHF stands for "ARM hard float", and is the name given to a Debian port for ARM processors (armv7+) that have hardware floating point support, which is found on most modern 32-bit ARM boards
ARM64("arm64", new String[] { "aarch64" }, new String[] { "arm64v8" }), // used by docker
RISCV64("riscv64", null, new String[] { "riscv64gc" }), // used by llvm
MIPS64LE("mips64le", null),
MIPS64LE("mips64le", new String[] { "mips64el" }),
S390X("s390x", null),
PPC64LE("ppc64le", null);

Expand Down
146 changes: 127 additions & 19 deletions src/main/java/com/fizzed/jne/JavaHomes.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,24 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import java.util.stream.Stream;

public class JavaHomes {
static private final Logger log = LoggerFactory.getLogger(JavaHome.class);

static public JavaHome fromDirectory(Path javaHomeDir) throws IOException {
return fromDirectory(javaHomeDir, false);
}

static public JavaHome fromDirectory(Path javaHomeDir, boolean requireReleaseFile) throws IOException {
if (!Files.isDirectory(javaHomeDir)) {
throw new FileNotFoundException("Java home directory " + javaHomeDir + " does not exist");
}
Expand All @@ -46,12 +49,22 @@ static public JavaHome fromDirectory(Path javaHomeDir) throws IOException {

// Test #1: bin/java exists?
final String javaExeFileName = NativeTarget.resolveExecutableFileName(thisOs, "java");
final Path javaExeFile = javaHomeDir.resolve("bin").resolve(javaExeFileName);
Path javaExeFile = javaHomeDir.resolve("bin").resolve(javaExeFileName);

if (!Files.isRegularFile(javaExeFile)) {
throw new FileNotFoundException("Java executable " + javaExeFile + " was not found in " + javaHomeDir);
}

// For old Java 8, sometimes we're in the "jre" directory, where we really need to probe the directory above
if ("jre".equalsIgnoreCase(javaHomeDir.getFileName().toString())) {
Path jdkHomeDir = javaHomeDir.getParent();
Path javaExeFileAlt = jdkHomeDir.resolve("bin").resolve(javaExeFileName);
if (Files.isRegularFile(javaExeFileAlt)) {
javaHomeDir = jdkHomeDir;
javaExeFile = javaExeFileAlt;
}
}

// Test #2: sometimes we can find a bin/java, especially if we're dealing with a directory simply on the PATH
// we need to make sure this directory "looks" like a typical java home, with a "lib" dir, etc.
final Path javaLibDir = javaHomeDir.resolve("lib");
Expand Down Expand Up @@ -79,28 +92,54 @@ static public JavaHome fromDirectory(Path javaHomeDir) throws IOException {
final Path releaseFile = javaHomeDir.resolve("release");
if (Files.isRegularFile(releaseFile)) {
releaseProperties = JavaHomes.readReleaseProperties(releaseFile);
}

String releaseJavaVersion = releaseProperties.get("JAVA_VERSION");
if (releaseJavaVersion != null) {
version = JavaVersion.parse(releaseJavaVersion);
if (releaseProperties == null) {
if (requireReleaseFile){
throw new FileNotFoundException("Java release file " + releaseFile + " was not found in " + javaHomeDir);
}

String releaseOs = releaseProperties.get("OS_NAME");
if (releaseOs != null) {
operatingSystem = OperatingSystem.resolve(releaseOs);
// otherwise, we could do "java -version" to try and detect it
try {
String versionOutput = executeJavaVersion(javaExeFile);
releaseProperties = readJavaVersionOutput(versionOutput);
} catch (Exception e) {
throw new IOException("Unable to execute -version command on " + javaExeFile, e);
}
}

String releaseJavaVersion = releaseProperties.get("JAVA_VERSION");
if (releaseJavaVersion != null) {
version = JavaVersion.parse(releaseJavaVersion);
}

String releaseArch = releaseProperties.get("OS_ARCH");
if (releaseArch != null) {
hardwareArchitecture = HardwareArchitecture.resolve(releaseArch);
// TODO: need special handling for "arm"
String releaseOs = releaseProperties.get("OS_NAME");
if (releaseOs != null) {
operatingSystem = OperatingSystem.resolve(releaseOs);
}

String releaseArch = releaseProperties.get("OS_ARCH");
if (releaseArch != null) {
hardwareArchitecture = HardwareArchitecture.resolve(releaseArch);
// special handling for "arm"
if (hardwareArchitecture == null) {
if ("arm".equalsIgnoreCase(releaseArch)) {
String sunArchAbi = releaseProperties.get("SUN_ARCH_ABI");
if ("gnueabihf".equalsIgnoreCase(sunArchAbi)) {
hardwareArchitecture = HardwareArchitecture.ARMHF;
} else if ("gnueabi".equalsIgnoreCase(sunArchAbi)) {
hardwareArchitecture = HardwareArchitecture.ARMEL;
}
}
}
}

vendor = releaseProperties.get("IMPLEMENTOR");
} else {
throw new FileNotFoundException("Java release file " + releaseFile + " was not found in " + javaHomeDir);
String releaseLibc = releaseProperties.get("LIBC");
if (releaseLibc != null) {
abi = ABI.resolve(releaseLibc);
}

vendor = releaseProperties.get("IMPLEMENTOR");

return new JavaHome(javaHomeDir, javaExeFile, javacExeFile, operatingSystem, hardwareArchitecture, abi, vendor, version, releaseProperties);
}

Expand All @@ -125,6 +164,75 @@ static public Map<String,String> readReleaseProperties(Path releaseFile) throws
return nameValues;
}

static public String executeJavaVersion(Path javaExeFile) throws IOException, InterruptedException {
final Process process = new ProcessBuilder()
.command(javaExeFile.toString(), "-version")
.redirectErrorStream(true)
.start();

// we do not need the input stream
process.getOutputStream().close();

// read all the output
final StringBuilder output = new StringBuilder();
final byte[] buf = new byte[1024];
try (InputStream input = process.getInputStream()) {
int read = 1;
while (read > 0) {
read = input.read(buf);
if (read > 0) {
output.append(new String(buf, 0, read, StandardCharsets.UTF_8));
}
}
}

// the exit value MUST be zero
process.waitFor(5, TimeUnit.SECONDS);

if (process.exitValue() != 0) {
throw new IOException("Version command failed with exit value " + process.exitValue());
}

return output.toString();
}

static public Map<String,String> readJavaVersionOutput(String versionOutput) throws IOException {
final String[] lines = versionOutput.split("\n");
final Map<String,String> nameValues = new HashMap<>();

// first line should have the version
String line1 = lines[0];
int doubleQuoteStartPos = line1.indexOf('"');
if (doubleQuoteStartPos > 0) {
int doubleQuoteEndPos = line1.indexOf('"', doubleQuoteStartPos+1);
if (doubleQuoteEndPos > doubleQuoteStartPos) {
String version = line1.substring(doubleQuoteStartPos+1, doubleQuoteEndPos);
nameValues.put("JAVA_VERSION", version.trim());
}
}

// second line should have the implementer version
if (lines.length > 1) {
String line2 = lines[1];
int implStartPos = line2.toLowerCase().indexOf("runtime environment ");
if (implStartPos > 0) {
int implEndPos = line2.indexOf(" (build", implStartPos+1);
if (implEndPos > implStartPos+23) {
String implementerVersion = line2.substring(implStartPos+20, implEndPos);

// remove ( and ) from it?
if (implementerVersion.charAt(0) == '(' && implementerVersion.charAt(implementerVersion.length()-1) == ')') {
implementerVersion = implementerVersion.substring(1, implementerVersion.length()-1);
}

nameValues.put("IMPLEMENTOR_VERSION", implementerVersion.trim());
}
}
}

return nameValues;
}

static public List<JavaHome> detect() throws Exception {
final NativeTarget nativeTarget = NativeTarget.detect();

Expand Down
25 changes: 21 additions & 4 deletions src/main/java/com/fizzed/jne/OperatingSystem.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,24 @@
public enum OperatingSystem {

WINDOWS("Windows", null),
MACOS("MacOS", new String[] { "osx" }),
MACOS("MacOS", new String[] { "osx" }, new String[] { "darwin" }),
LINUX("Linux", null),
FREEBSD("FreeBSD", null),
OPENBSD("OpenBSD", null),
SOLARIS("Solaris", null);
SOLARIS("Solaris", new String[] { "sun" });

private final String descriptor;
private final String[] aliases;
private final String[] extraAliases;

OperatingSystem(String descriptor, String[] aliases) {
this(descriptor, aliases, null);
}

OperatingSystem(String descriptor, String[] aliases, String[] extraAliases) {
this.descriptor = descriptor;
this.aliases = aliases;
this.extraAliases = extraAliases;
}

public String getDescriptor() {
Expand All @@ -48,18 +54,29 @@ public String[] getAliases() {
return aliases;
}

public String[] getExtraAliases() {
return extraAliases;
}

static public OperatingSystem resolve(String value) {
for (OperatingSystem os : OperatingSystem.values()) {
if (os.name().equalsIgnoreCase(value)) {
return os;
}
if (os.getAliases() != null) {
for (String alias : os.getAliases()) {
if (os.aliases != null) {
for (String alias : os.aliases) {
if (alias.equalsIgnoreCase(value)) {
return os;
}
}
}
if (os.extraAliases != null) {
for (String extraAlias : os.extraAliases) {
if (extraAlias.equalsIgnoreCase(value)) {
return os;
}
}
}
}
return null;
}
Expand Down
19 changes: 13 additions & 6 deletions src/main/java/com/fizzed/jne/PlatformInfo.java
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ static OperatingSystem doDetectOperatingSystem() {
}

static OperatingSystem detectOperatingSystemFromValues(String osName) {
if (osName != null) {
return OperatingSystem.resolve(osName);
/*if (osName != null) {
osName = osName.toLowerCase();
if (osName.contains("windows")) {
return OperatingSystem.WINDOWS;
Expand All @@ -82,7 +83,7 @@ static OperatingSystem detectOperatingSystemFromValues(String osName) {
return OperatingSystem.OPENBSD;
}
}
return null;
return null;*/
}

//
Expand Down Expand Up @@ -135,15 +136,21 @@ static HardwareArchitecture detectHardwareArchitectureFromValues(
abiType = abiType != null ? abiType.toLowerCase() : "none";
bootLibPath = bootLibPath != null ? bootLibPath.toLowerCase() : "none";

if (osArch.contains("amd64") || osArch.contains("x86_64")) {
// delegate most of the lookup to the HW enum
HardwareArchitecture hardwareArchitecture = HardwareArchitecture.resolve(osArch);
if (hardwareArchitecture != null) {
return hardwareArchitecture;
}

/*if (osArch.contains("amd64") || osArch.contains("x86_64")) {
return HardwareArchitecture.X64;
} else if (osArch.contains("i386") || osArch.contains("i686") || osArch.contains("x86")) {
return HardwareArchitecture.X32;
} else if (osArch.contains("aarch64")) {
return HardwareArchitecture.ARM64;
} else if (osArch.contains("armv7l")) {
return HardwareArchitecture.ARMHF;
} else if (osArch.contains("arm") || osArch.contains("aarch32")) {
} else*/ if (osArch.contains("arm") || osArch.contains("aarch32")) {
// unfortunately, this arch is used for ARMEL vs ARMHF, we can leverage the mapped files on linux to help differentiate
log.trace("System property arch [{}] is ambiguous, will try a few workarounds", osArch);
// abitype? e.g. gnueabihf
Expand All @@ -168,15 +175,15 @@ static HardwareArchitecture detectHardwareArchitectureFromValues(
return linuxMappedFilesResult.getArch();
}
// the most common is likely hard float
} else if (osArch.contains("riscv64")) {
} /*else if (osArch.contains("riscv64")) {
return HardwareArchitecture.RISCV64;
} else if (osArch.contains("s390x")) {
return HardwareArchitecture.S390X;
} else if (osArch.contains("ppc64le")) {
return HardwareArchitecture.PPC64LE;
} else if (osArch.contains("mips64el") || osArch.contains("mips64le")) {
return HardwareArchitecture.MIPS64LE;
}
}*/
}
return null;
}
Expand Down
Loading

0 comments on commit 9eb2207

Please sign in to comment.