From 5fad436824c3688b52a2a6dc87367ece151f7ce3 Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Mon, 8 Aug 2016 20:39:26 +0200 Subject: [PATCH] [platform] improves JDK version detection (vaguely related to IDEA-159314) --- .../jps/model/java/JdkVersionDetector.java | 9 +- .../java/impl/JdkVersionDetectorImpl.java | 273 +++++++----------- .../jps/model/JdkVersionDetectorTest.kt | 43 +++ .../projectRoots/impl/SdkVersionUtil.java | 35 +-- 4 files changed, 160 insertions(+), 200 deletions(-) create mode 100644 jps/model-impl/testSrc/org/jetbrains/jps/model/JdkVersionDetectorTest.kt diff --git a/jps/model-api/src/org/jetbrains/jps/model/java/JdkVersionDetector.java b/jps/model-api/src/org/jetbrains/jps/model/java/JdkVersionDetector.java index 8bc0c0580229..64f60bd390df 100644 --- a/jps/model-api/src/org/jetbrains/jps/model/java/JdkVersionDetector.java +++ b/jps/model-api/src/org/jetbrains/jps/model/java/JdkVersionDetector.java @@ -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; } } -} +} \ No newline at end of file diff --git a/jps/model-impl/src/org/jetbrains/jps/model/java/impl/JdkVersionDetectorImpl.java b/jps/model-impl/src/org/jetbrains/jps/model/java/impl/JdkVersionDetectorImpl.java index 9e0edb9e4da8..fdcd6cf9d288 100644 --- a/jps/model-impl/src/org/jetbrains/jps/model/java/impl/JdkVersionDetectorImpl.java +++ b/jps/model-impl/src/org/jetbrains/jps/model/java/impl/JdkVersionDetectorImpl.java @@ -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 myLines; + + public VersionOutputReader(@NotNull InputStream stream, @NotNull ActionRunner runner) { + super(stream, CharsetToolkit.getDefaultSystemCharset(), OPTIONS); + myRunner = runner; + myLines = new CopyOnWriteArrayList(); + 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 myVersionString = new AtomicReference(); - private final AtomicReference myBitness = new AtomicReference(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; - } - } -} +} \ No newline at end of file diff --git a/jps/model-impl/testSrc/org/jetbrains/jps/model/JdkVersionDetectorTest.kt b/jps/model-impl/testSrc/org/jetbrains/jps/model/JdkVersionDetectorTest.kt new file mode 100644 index 000000000000..357819b671ac --- /dev/null +++ b/jps/model-impl/testSrc/org/jetbrains/jps/model/JdkVersionDetectorTest.kt @@ -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) + } +} \ No newline at end of file diff --git a/platform/lang-api/src/com/intellij/openapi/projectRoots/impl/SdkVersionUtil.java b/platform/lang-api/src/com/intellij/openapi/projectRoots/impl/SdkVersionUtil.java index a95d02cdf22c..8c38f0717fb9 100644 --- a/platform/lang-api/src/com/intellij/openapi/projectRoots/impl/SdkVersionUtil.java +++ b/platform/lang-api/src/com/intellij/openapi/projectRoots/impl/SdkVersionUtil.java @@ -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); } -} +} \ No newline at end of file