[platform] improves JDK version detection

(vaguely related to IDEA-159314)
This commit is contained in:
Roman Shevchenko
2016-08-08 20:40:05 +02:00
parent 5d83effba9
commit 5fad436824
4 changed files with 160 additions and 200 deletions
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
* Copyright 2000-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -49,11 +49,6 @@ public abstract class JdkVersionDetector {
@Nullable
public abstract JdkVersionInfo detectJdkVersionInfo(@NotNull String homePath, @NotNull ActionRunner actionRunner);
@Deprecated
@Nullable
public abstract String readVersionFromProcessOutput(@NotNull String homePath, @NotNull String[] command, String versionLineMarker,
@NotNull ActionRunner actionRunner);
//todo[nik] replace with a service with different implementations for IDE process and for JPS process (need to exclude jps-builders module from IDEA classpath)
public interface ActionRunner {
Future<?> run(Runnable runnable);
@@ -78,4 +73,4 @@ public abstract class JdkVersionDetector {
return myBitness;
}
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
* Copyright 2000-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,14 +17,23 @@ package org.jetbrains.jps.model.java.impl;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Bitness;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.CharsetToolkit;
import com.intellij.util.io.BaseOutputReader;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jps.model.java.JdkVersionDetector;
import org.jetbrains.jps.service.SharedThreadPool;
import java.io.*;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicReference;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
@@ -47,42 +56,31 @@ public class JdkVersionDetectorImpl extends JdkVersionDetector {
}
@Nullable
public String detectJdkVersion(@NotNull String homePath, @NotNull final ActionRunner actionRunner) {
final File path = new File(homePath, "jre/lib/rt.jar");
try {
JarFile runtimeArchive;
public String detectJdkVersion(@NotNull String homePath, @NotNull ActionRunner runner) {
File rtFile = new File(homePath, "jre/lib/rt.jar");
if (rtFile.isFile()) {
try {
runtimeArchive = new JarFile(path, false);
}
catch (IOException e) {
JarFile rtJar = new JarFile(rtFile, false);
try {
runtimeArchive = new JarFile(path.getParentFile(), false);
}
catch (IOException e1) {
// jdk9 case. Alternatively, if jrt-fs.jar is not available, we could read the 'release' file
runtimeArchive = new JarFile(new File(homePath, "jrt-fs.jar"));
}
}
try {
final Manifest manifest = runtimeArchive.getManifest();
if (manifest != null) {
final String version = manifest.getMainAttributes().getValue("Implementation-Version");
if (version != null) {
return "java version \"" + version + "\"";
Manifest manifest = rtJar.getManifest();
if (manifest != null) {
String version = manifest.getMainAttributes().getValue("Implementation-Version");
if (version != null) {
return "java version \"" + version + "\"";
}
}
}
finally {
rtJar.close();
}
}
finally {
runtimeArchive.close();
catch (IOException e) {
LOG.info(e);
}
}
catch (IOException ignored) {
}
JdkVersionInfo info = detectJdkVersionInfo(homePath, actionRunner);
if (info != null) {
return info.getVersion();
}
return null;
JdkVersionInfo info = detectJdkVersionInfo(homePath, runner);
return info != null ? info.getVersion() : null;
}
@Override
@@ -91,33 +89,34 @@ public class JdkVersionDetectorImpl extends JdkVersionDetector {
}
@Override
public JdkVersionInfo detectJdkVersionInfo(@NotNull String homePath, @NotNull ActionRunner actionRunner) {
String[] command = {homePath + File.separator + "bin" + File.separator + "java", "-version"};
return readVersionInfoFromProcessOutput(homePath, command, null, actionRunner);
}
public String readVersionFromProcessOutput(@NotNull String homePath, @NotNull String[] command, String versionLineMarker,
@NotNull ActionRunner actionRunner) {
JdkVersionInfo info = readVersionInfoFromProcessOutput(homePath, command, versionLineMarker, actionRunner);
if (info != null) {
return info.getVersion();
}
return null;
}
private static JdkVersionInfo readVersionInfoFromProcessOutput(@NotNull String homePath, @NotNull String[] command, String versionLineMarker, @NotNull ActionRunner actionRunner) {
if (!new File(homePath).exists()) {
return null;
}
try {
//noinspection HardCodedStringLiteral
Process process = Runtime.getRuntime().exec(command);
VersionParsingThread parsingThread = new VersionParsingThread(process.getErrorStream(), versionLineMarker);
final Future<?> parsingThreadFuture = actionRunner.run(parsingThread);
ReadStreamThread readThread = new ReadStreamThread(process.getInputStream());
actionRunner.run(readThread);
public JdkVersionInfo detectJdkVersionInfo(@NotNull String homePath, @NotNull ActionRunner runner) {
File releaseFile = new File(homePath, "release");
if (releaseFile.isFile()) {
try {
Properties p = new Properties();
p.load(new FileInputStream(releaseFile));
String version = p.getProperty("JAVA_FULL_VERSION", p.getProperty("JAVA_VERSION"));
if (version != null) {
version = StringUtil.unquoteString(version);
int i = version.indexOf('+');
if (i > 0) {
version = version.substring(0, i);
}
String arch = StringUtil.unquoteString(p.getProperty("OS_ARCH", ""));
boolean x64 = "x86_64".equals(arch) || "amd64".equals(arch);
return new JdkVersionInfo("java version \"" + version + "\"", x64 ? Bitness.x64 : Bitness.x32);
}
}
catch (IOException e) {
LOG.info(e);
}
}
String javaExe = homePath + File.separator + "bin" + File.separator + (SystemInfo.isWindows ? "java.exe" : "java");
if (new File(javaExe).canExecute()) {
try {
Process process = new ProcessBuilder(javaExe, "-version").redirectErrorStream(true).start();
VersionOutputReader reader = new VersionOutputReader(process.getInputStream(), runner);
try {
process.waitFor();
}
@@ -125,128 +124,62 @@ public class JdkVersionDetectorImpl extends JdkVersionDetector {
LOG.info(e);
process.destroy();
}
return reader.getVersionInfo();
}
finally {
try {
parsingThreadFuture.get();
}
catch (Exception e) {
LOG.info(e);
}
}
String version = parsingThread.getVersion();
if (version != null) {
return new JdkVersionInfo(version, parsingThread.getBitness());
catch (IOException e) {
LOG.info(e);
}
}
catch (IOException ex) {
LOG.info(ex);
}
return null;
}
public static class ReadStreamThread implements Runnable {
private final InputStream myStream;
private static class VersionOutputReader extends BaseOutputReader {
private static final BaseOutputReader.Options OPTIONS = new BaseOutputReader.Options() {
@Override public SleepingPolicy policy() { return SleepingPolicy.BLOCKING; }
@Override public boolean splitToLines() { return true; }
@Override public boolean sendIncompleteLines() { return false; }
@Override public boolean withSeparators() { return false; }
};
protected ReadStreamThread(InputStream stream) {
myStream = stream;
private final ActionRunner myRunner;
private final List<String> myLines;
public VersionOutputReader(@NotNull InputStream stream, @NotNull ActionRunner runner) {
super(stream, CharsetToolkit.getDefaultSystemCharset(), OPTIONS);
myRunner = runner;
myLines = new CopyOnWriteArrayList<String>();
start("java -version");
}
public void run() {
try {
while (true) {
int b = myStream.read();
if (b == -1) break;
@NotNull
@Override
protected Future<?> executeOnPooledThread(@NotNull Runnable runnable) {
return myRunner.run(runnable);
}
@Override
protected void onTextAvailable(@NotNull String text) {
myLines.add(text);
}
@Nullable
public JdkVersionInfo getVersionInfo() {
String version = null;
Bitness arch = Bitness.x32;
for (String line : myLines) {
if (line.contains("version")) {
if (version == null) {
version = line;
}
}
else if (line.contains("64-Bit") || line.contains("x86_64") || line.contains("amd64")) {
arch = Bitness.x64;
}
}
catch (IOException e) {
LOG.info(e);
}
return version != null ? new JdkVersionInfo(version, arch) : null;
}
}
public static class VersionParsingThread implements Runnable {
private Reader myReader;
private final InputStream myStream;
private boolean mySkipLF = false;
private final String myVersionLineMarker;
private final AtomicReference<String> myVersionString = new AtomicReference<String>();
private final AtomicReference<Bitness> myBitness = new AtomicReference<Bitness>(Bitness.x32);
private static final String VERSION_LINE_MARKER = "version";
private static final String BITNESS_64_MARKER = "64-Bit";
protected VersionParsingThread(InputStream input, String versionLineMarker) {
myStream = input;
myVersionLineMarker = versionLineMarker != null ? versionLineMarker : VERSION_LINE_MARKER;
}
Bitness getBitness() {
return myBitness.get();
}
String getVersion() {
return myVersionString.get();
}
public void run() {
try {
myReader = new InputStreamReader(myStream);
while (true) {
String line = readLine();
if (line == null) return;
if (line.contains(myVersionLineMarker)) {
myVersionString.set(line);
}
if (line.contains(BITNESS_64_MARKER)) {
myBitness.set(Bitness.x64);
}
}
}
catch (IOException e) {
LOG.info(e);
}
finally {
if (myReader != null){
try {
myReader.close();
}
catch (IOException e) {
LOG.info(e);
}
}
}
}
private String readLine() throws IOException {
boolean first = true;
StringBuilder buffer = new StringBuilder();
while (true) {
int c = myReader.read();
if (c == -1) break;
first = false;
if (c == '\n') {
if (mySkipLF) {
mySkipLF = false;
continue;
}
break;
}
else if (c == '\r') {
mySkipLF = true;
break;
}
else {
mySkipLF = false;
buffer.append((char)c);
}
}
if (first) return null;
String s = buffer.toString();
//if (Diagnostic.TRACE_ENABLED){
// Diagnostic.trace(s);
//}
return s;
}
}
}
}
@@ -0,0 +1,43 @@
/*
* Copyright 2000-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.jps.model
import com.intellij.openapi.util.Bitness
import com.intellij.openapi.util.SystemInfo
import org.jetbrains.jps.model.java.JdkVersionDetector
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Test
import java.util.concurrent.Executors
class JdkVersionDetectorTest {
private val service = Executors.newFixedThreadPool(3)
@After fun tearDown() = service.shutdown()
@Test fun detectJdkVersion() {
val jdkHome = System.getProperty("java.home")
val jdkVersion = JdkVersionDetector.getInstance().detectJdkVersion(jdkHome, { service.submit(it) })
assertEquals("java version \"${SystemInfo.JAVA_VERSION}\"", jdkVersion)
}
@Test fun detectJdkVersionInfo() {
val jdkHome = System.getProperty("java.home")
val jdkVersion = JdkVersionDetector.getInstance().detectJdkVersionInfo(jdkHome, { service.submit(it) })
assertEquals("java version \"${SystemInfo.JAVA_VERSION}\"", jdkVersion?.version)
assertEquals(if (SystemInfo.is64Bit) Bitness.x64 else Bitness.x32, jdkVersion?.bitness)
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
* Copyright 2000-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,38 +13,27 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Created by IntelliJ IDEA.
* User: Anna.Kozlova
* Date: 12-Aug-2006
* Time: 21:25:38
*/
package com.intellij.openapi.projectRoots.impl;
import com.intellij.openapi.application.ApplicationManager;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jps.model.java.JdkVersionDetector;
import java.util.concurrent.Future;
/**
* @author Anna.Kozlova
* @since 12-Aug-2006
*/
public class SdkVersionUtil {
private static final JdkVersionDetector.ActionRunner ACTION_RUNNER = new JdkVersionDetector.ActionRunner() {
@Override
public Future<?> run(Runnable runnable) {
return ApplicationManager.getApplication().executeOnPooledThread(runnable);
}
};
private static final JdkVersionDetector.ActionRunner ACTION_RUNNER = (r) -> ApplicationManager.getApplication().executeOnPooledThread(r);
private SdkVersionUtil() {
}
private SdkVersionUtil() { }
@Deprecated
/** @deprecated use {@link #detectJdkVersion(String)} to be removed in IDEA 2018 */
@Nullable
public static String readVersionFromProcessOutput(@NotNull String homePath, @NonNls @NotNull String[] command, @NonNls String versionLineMarker) {
return JdkVersionDetector.getInstance().readVersionFromProcessOutput(homePath, command, versionLineMarker, ACTION_RUNNER);
@SuppressWarnings("unused")
public static String readVersionFromProcessOutput(@NotNull String homePath, @NotNull String[] command, String versionLineMarker) {
return JdkVersionDetector.getInstance().detectJdkVersion(homePath, ACTION_RUNNER);
}
@Nullable
@@ -56,4 +45,4 @@ public class SdkVersionUtil {
public static JdkVersionDetector.JdkVersionInfo getJdkVersionInfo(@NotNull String homePath) {
return JdkVersionDetector.getInstance().detectJdkVersionInfo(homePath, ACTION_RUNNER);
}
}
}