diff --git a/org.eclipse.jdt.debug.tests/multirelease/src/p/Main.java b/org.eclipse.jdt.debug.tests/multirelease/src/p/Main.java new file mode 100644 index 0000000000..ebd66e9111 --- /dev/null +++ b/org.eclipse.jdt.debug.tests/multirelease/src/p/Main.java @@ -0,0 +1,10 @@ +package p; + +public class Main { + + public static void main(String[] args) { + System.out.println("Java: "+java.lang.Runtime.version().feature()); + new X().greet(); + } + +} diff --git a/org.eclipse.jdt.debug.tests/multirelease/src/p/X.java b/org.eclipse.jdt.debug.tests/multirelease/src/p/X.java new file mode 100644 index 0000000000..1a92bdbaf7 --- /dev/null +++ b/org.eclipse.jdt.debug.tests/multirelease/src/p/X.java @@ -0,0 +1,7 @@ +package p; + +public class X { + public void greet() { + System.out.println("X: 11"); + } +} diff --git a/org.eclipse.jdt.debug.tests/multirelease/src/p/Y.java b/org.eclipse.jdt.debug.tests/multirelease/src/p/Y.java new file mode 100644 index 0000000000..182535c40c --- /dev/null +++ b/org.eclipse.jdt.debug.tests/multirelease/src/p/Y.java @@ -0,0 +1,7 @@ +package p; + +public class Y { + public void y() { + System.out.println("Y: 11"); + } +} diff --git a/org.eclipse.jdt.debug.tests/multirelease/src17/p/X.java b/org.eclipse.jdt.debug.tests/multirelease/src17/p/X.java new file mode 100644 index 0000000000..547550ee77 --- /dev/null +++ b/org.eclipse.jdt.debug.tests/multirelease/src17/p/X.java @@ -0,0 +1,8 @@ +package p; + +public class X { + public void greet() { + System.out.println("X: 17"); + new Y().y(); + } +} diff --git a/org.eclipse.jdt.debug.tests/multirelease/src17/p/Z.java b/org.eclipse.jdt.debug.tests/multirelease/src17/p/Z.java new file mode 100644 index 0000000000..8de12387a8 --- /dev/null +++ b/org.eclipse.jdt.debug.tests/multirelease/src17/p/Z.java @@ -0,0 +1,7 @@ +package p; + +public class Z { + public static void z() { + System.out.println("Z: 17"); + } +} diff --git a/org.eclipse.jdt.debug.tests/multirelease/src21/p/Y.java b/org.eclipse.jdt.debug.tests/multirelease/src21/p/Y.java new file mode 100644 index 0000000000..61ccc9e0f5 --- /dev/null +++ b/org.eclipse.jdt.debug.tests/multirelease/src21/p/Y.java @@ -0,0 +1,8 @@ +package p; + +public class Y { + public void y() { + System.out.println("Y: 21"); + Z.z(); + } +} diff --git a/org.eclipse.jdt.debug.tests/test plugin/org/eclipse/jdt/debug/testplugin/JavaProjectHelper.java b/org.eclipse.jdt.debug.tests/test plugin/org/eclipse/jdt/debug/testplugin/JavaProjectHelper.java index cbbfe71924..b2d0a42829 100644 --- a/org.eclipse.jdt.debug.tests/test plugin/org/eclipse/jdt/debug/testplugin/JavaProjectHelper.java +++ b/org.eclipse.jdt.debug.tests/test plugin/org/eclipse/jdt/debug/testplugin/JavaProjectHelper.java @@ -38,6 +38,7 @@ import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Path; +import org.eclipse.jdt.core.IClasspathAttribute; import org.eclipse.jdt.core.IClasspathEntry; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IPackageFragmentRoot; @@ -111,6 +112,11 @@ public class JavaProjectHelper { */ public static final IPath TEST_25_SRC_DIR = new Path("java25"); + /** + * path to the multirelease test source + */ + public static final IPath TEST_MR_SRC_DIR = new Path("multirelease"); + /** * path to the compiler error java file */ @@ -323,9 +329,12 @@ public static void delete(IJavaProject jproject) throws CoreException { /** * Adds a new source container specified by the container name to the source path of the specified project + * + * @param extra + * optional extra classpath attributes * @return the package fragment root of the container name */ - public static IPackageFragmentRoot addSourceContainer(IJavaProject jproject, String containerName) throws CoreException { + public static IPackageFragmentRoot addSourceContainer(IJavaProject jproject, String containerName, IClasspathAttribute... extra) throws CoreException { IProject project= jproject.getProject(); IContainer container= null; if (containerName == null || containerName.length() == 0) { @@ -339,7 +348,7 @@ public static IPackageFragmentRoot addSourceContainer(IJavaProject jproject, Str } IPackageFragmentRoot root= jproject.getPackageFragmentRoot(container); - IClasspathEntry cpe= JavaCore.newSourceEntry(root.getPath()); + IClasspathEntry cpe = JavaCore.newSourceEntry(root.getPath(), ClasspathEntry.INCLUDE_ALL, ClasspathEntry.EXCLUDE_NONE, null, extra); addToClasspath(jproject, cpe); return root; } diff --git a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AbstractDebugTest.java b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AbstractDebugTest.java index 242873a75e..26b68454c1 100644 --- a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AbstractDebugTest.java +++ b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AbstractDebugTest.java @@ -184,6 +184,7 @@ public abstract class AbstractDebugTest extends TestCase implements IEvaluation public static final String TWENTYFOUR_PROJECT_NAME = "Two_Four"; public static final String TWENTYFIVE_PROJECT_NAME = "Two_Five"; public static final String BOUND_JRE_PROJECT_NAME = "BoundJRE"; + public static final String MR_PROJECT_NAME = "MR"; public static final String CLONE_SUFFIX = "Clone"; final String[] LAUNCH_CONFIG_NAMES_1_4 = { "LargeSourceFile", "LotsOfFields", @@ -248,6 +249,7 @@ public abstract class AbstractDebugTest extends TestCase implements IEvaluation private static boolean loadedEE = false; private static boolean loadedJRE = false; private static boolean loadedMulti = false; + private static boolean loadedMR; private static boolean welcomeClosed = false; /** @@ -285,6 +287,8 @@ protected void setUp() throws Exception { loadedEE = pro.exists(); pro = ResourcesPlugin.getWorkspace().getRoot().getProject(MULTI_OUTPUT_PROJECT_NAME); loadedMulti = pro.exists(); + pro = ResourcesPlugin.getWorkspace().getRoot().getProject(MR_PROJECT_NAME); + loadedMR = pro.exists(); assertWelcomeScreenClosed(); } @@ -626,6 +630,40 @@ synchronized void assert21Project() { } } + synchronized void assertMRProject() { + IJavaProject jp = null; + ArrayList cfgs = new ArrayList<>(1); + try { + if (!loadedMR) { + jp = createProject(MR_PROJECT_NAME, JavaProjectHelper.TEST_MR_SRC_DIR.toString(), JavaProjectHelper.JAVA_SE_21_EE_NAME, false); + jp.setOption(JavaCore.COMPILER_RELEASE, JavaCore.ENABLED); + jp.setOption(JavaCore.COMPILER_COMPLIANCE, "11"); + jp.setOption(JavaCore.COMPILER_SOURCE, "11"); + jp.setOption(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM, "11"); + IPackageFragmentRoot src17 = JavaProjectHelper.addSourceContainer(jp, "src17", JavaCore.newClasspathAttribute(IClasspathAttribute.RELEASE, "17")); + IPackageFragmentRoot src21 = JavaProjectHelper.addSourceContainer(jp, "src21", JavaCore.newClasspathAttribute(IClasspathAttribute.RELEASE, "21")); + File root = JavaTestPlugin.getDefault().getFileInPlugin(JavaProjectHelper.TEST_MR_SRC_DIR); + JavaProjectHelper.importFilesFromDirectory(new File(root, src17.getPath().lastSegment()), src17.getPath(), null); + JavaProjectHelper.importFilesFromDirectory(new File(root, src21.getPath().lastSegment()), src21.getPath(), null); + cfgs.add(createLaunchConfiguration(jp, "p.Main")); + loadedMR = true; + waitForBuild(); + } + } catch (Exception e) { + try { + if (jp != null) { + jp.getProject().delete(true, true, null); + for (int i = 0; i < cfgs.size(); i++) { + cfgs.get(i).delete(); + } + } + } catch (CoreException ce) { + // ignore + } + handleProjectCreationException(e, MR_PROJECT_NAME, jp); + } + } + synchronized void assert23Project() { IJavaProject jp = null; ArrayList cfgs = new ArrayList<>(1); @@ -1017,6 +1055,16 @@ protected IJavaProject get21Project() { return getJavaProject(TWENTYONE_PROJECT_NAME); } + /** + * Returns the 'multirelease' project, used for Multirelease tests. + * + * @return the test project + */ + protected IJavaProject getMultireleaseProject() { + assertMRProject(); + return getJavaProject(MR_PROJECT_NAME); + } + /** * Returns the 'Two_Three' project, used for Java 23 tests. * @@ -1117,7 +1165,12 @@ protected IJavaProject createProject(String name, String contentpath, String ee, IJavaProject jp = JavaProjectHelper.createJavaProject(name, JavaProjectHelper.BIN_DIR); IPackageFragmentRoot src = JavaProjectHelper.addSourceContainer(jp, JavaProjectHelper.SRC_DIR); File root = JavaTestPlugin.getDefault().getFileInPlugin(new Path(contentpath)); - JavaProjectHelper.importFilesFromDirectory(root, src.getPath(), null); + File srcInRoot = new File(root, src.getPath().lastSegment()); + if (srcInRoot.isDirectory()) { + JavaProjectHelper.importFilesFromDirectory(srcInRoot, src.getPath(), null); + } else { + JavaProjectHelper.importFilesFromDirectory(root, src.getPath(), null); + } // add the EE library IVMInstall vm = JavaRuntime.getDefaultVMInstall(); diff --git a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AutomatedSuite.java b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AutomatedSuite.java index eadb81da74..f9dae98e91 100644 --- a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AutomatedSuite.java +++ b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AutomatedSuite.java @@ -87,6 +87,7 @@ import org.eclipse.jdt.debug.tests.core.LiteralTests17; import org.eclipse.jdt.debug.tests.core.LocalVariableTests; import org.eclipse.jdt.debug.tests.core.ModuleOptionsTests; +import org.eclipse.jdt.debug.tests.core.MultiReleaseLaunchTests; import org.eclipse.jdt.debug.tests.core.ProcessTests; import org.eclipse.jdt.debug.tests.core.ResolveRuntimeClasspathTests; import org.eclipse.jdt.debug.tests.core.RuntimeClasspathEntryTests; @@ -291,6 +292,7 @@ public AutomatedSuite() { addTest(new TestSuite(WorkingDirectoryTests.class)); addTest(new TestSuite(EventDispatcherTest.class)); addTest(new TestSuite(SyntheticVariableTests.class)); + addTest(new TestSuite(MultiReleaseLaunchTests.class)); // Refactoring tests //TODO: project rename diff --git a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/core/MultiReleaseLaunchTests.java b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/core/MultiReleaseLaunchTests.java new file mode 100644 index 0000000000..7d79c1f002 --- /dev/null +++ b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/core/MultiReleaseLaunchTests.java @@ -0,0 +1,186 @@ +/******************************************************************************* + * Copyright (c) 2025 Christoph Läubrich and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Christoph Läubrich - initial API and implementation + *******************************************************************************/ +package org.eclipse.jdt.debug.tests.core; + +import java.io.File; +import java.io.StringReader; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Properties; +import java.util.Set; +import java.util.UUID; + +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.debug.core.DebugPlugin; +import org.eclipse.debug.core.ILaunchConfiguration; +import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy; +import org.eclipse.debug.ui.DebugUITools; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.debug.core.IJavaDebugTarget; +import org.eclipse.jdt.debug.tests.ui.AbstractDebugUiTests; +import org.eclipse.jdt.internal.launching.DetectVMInstallationsJob; +import org.eclipse.jdt.launching.IVMInstall; +import org.eclipse.jdt.launching.IVMInstall2; +import org.eclipse.jdt.launching.IVMInstallType; +import org.eclipse.jdt.launching.JavaRuntime; +import org.eclipse.jdt.launching.VMStandin; +import org.eclipse.jface.text.IDocument; +import org.eclipse.ui.console.IConsole; +import org.eclipse.ui.console.TextConsole; + +/** + * IMPORTANT This test requires some different JVM installs to be present (see {@link #JAVA_11}, {@link #JAVA_17}, {@link #JAVA_21})) if such + * JVMs can not be found, the test will fail! One can specify a basedir to search for such jvms with the {@link #JVM_SEARCH_BASE} system property. + */ +public class MultiReleaseLaunchTests extends AbstractDebugUiTests { + + private static final String JVM_SEARCH_BASE = "MultiReleaseLaunchTests.rootDir"; + private static final RequiredJavaVersion JAVA_11 = new RequiredJavaVersion(11, 16); + private static final RequiredJavaVersion JAVA_17 = new RequiredJavaVersion(17, 20); + private static final RequiredJavaVersion JAVA_21 = new RequiredJavaVersion(21, Integer.MAX_VALUE); + + private List disposeVms = new ArrayList<>(); + + public MultiReleaseLaunchTests(String name) { + super(name); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + final Set existingLocations = new HashSet<>(); + List requiredJavaVersions = new ArrayList<>(List.of(JAVA_11, JAVA_17, JAVA_21)); + removeExistingJavaVersions(requiredJavaVersions, existingLocations); + if (!requiredJavaVersions.isEmpty()) { + final File rootDir = new File(System.getProperty(JVM_SEARCH_BASE, "/opt/tools/java/openjdk/")); + final List locations = new ArrayList<>(); + final List types = new ArrayList<>(); + DetectVMInstallationsJob.search(rootDir, locations, types, existingLocations, new NullProgressMonitor()); + for (int i = 0; i < locations.size(); i++) { + File location = locations.get(i); + IVMInstallType type = types.get(i); + String id = "MultiReleaseLaunchTests-" + UUID.randomUUID() + "-" + i; + VMStandin workingCopy = new VMStandin(type, id); + workingCopy.setInstallLocation(location); + workingCopy.setName(id); + IVMInstall install = workingCopy.convertToRealVM(); + if (removeIfMatch(requiredJavaVersions, install)) { + disposeVms.add(() -> type.disposeVMInstall(id)); + } else { + type.disposeVMInstall(id); + } + } + } + assertTrue("The following java versions are required by this test but can not be found: " + + requiredJavaVersions, requiredJavaVersions.isEmpty()); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + disposeVms.forEach(Runnable::run); + } + + @Override + protected IJavaProject getProjectContext() { + return getMultireleaseProject(); + } + + public void testMultiReleaseLaunch() throws Exception { + ILaunchConfiguration config = getLaunchConfiguration("p.Main"); + Properties result = launchAndReadResult(config, 11); + assertTrue("Was not launched with a proper Java installation " + result, JAVA_11.matches(result.getProperty("Java"))); + assertEquals("X should be executed from Java 11 version: " + result, "11", result.get("X")); + assertNull("Y should not be executed from Java 11 version: " + result, result.get("Y")); + assertNull("Z should not be executed from Java 11 version: " + result, result.get("Z")); + Properties result17 = launchAndReadResult(config, 17); + assertTrue("Was not launched with a proper Java installation " + result17, JAVA_17.matches(result17.getProperty("Java"))); + assertEquals("X should be executed from Java 17 version: " + result17, "17", result17.get("X")); + assertEquals("Y should be executed from Java 11 version: " + result17, "11", result17.get("Y")); + assertNull("Z should not be executed from Java 17 version: " + result17, result17.get("Z")); + Properties result21 = launchAndReadResult(config, 21); + assertTrue("Was not launched with a proper Java installation " + result21, JAVA_21.matches(result21.getProperty("Java"))); + assertEquals("X should be executed from Java 17 version: " + result21, "17", result21.get("X")); + assertEquals("Y should be executed from Java 21 version: " + result21, "21", result21.get("Y")); + assertEquals("Z should be executed from Java 17 version: " + result21, "17", result21.get("Z")); + } + + private Properties launchAndReadResult(ILaunchConfiguration config, int javaVersion) throws Exception { + ILaunchConfigurationWorkingCopy workingCopy = config.getWorkingCopy(); + workingCopy.setAttribute("org.eclipse.jdt.launching.JRE_CONTAINER", "org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-" + + javaVersion + "/"); + Properties properties = new Properties(); + IJavaDebugTarget target = launchAndTerminate(workingCopy.doSave(), DEFAULT_TIMEOUT); + processUiEvents(); + final IConsole console = DebugUITools.getConsole(target.getProcess()); + final TextConsole textConsole = (TextConsole) console; + final IDocument consoleDocument = textConsole.getDocument(); + String content = consoleDocument.get(); + properties.load(new StringReader(content)); + DebugPlugin.getDefault().getLaunchManager().removeLaunch(target.getLaunch()); + return properties; + } + + private static int getJavaVersion(IVMInstall install) { + if (install instanceof IVMInstall2 vm) { + try { + String javaVersion = vm.getJavaVersion().split("\\.")[0]; //$NON-NLS-1$ + return Integer.parseInt(javaVersion); + } catch (RuntimeException rte) { + // can't know then... + } + } + return -1; + } + + private static void removeExistingJavaVersions(Collection requiredJavaVersions, Set existingLocations) { + IVMInstallType[] installTypes = JavaRuntime.getVMInstallTypes(); + for (IVMInstallType installType : installTypes) { + IVMInstall[] vmInstalls = installType.getVMInstalls(); + for (IVMInstall install : vmInstalls) { + if (requiredJavaVersions.isEmpty()) { + return; + } + existingLocations.add(install.getInstallLocation()); + removeIfMatch(requiredJavaVersions, install); + } + } + } + + protected static boolean removeIfMatch(Collection requiredJavaVersions, IVMInstall install) { + int javaVersion = getJavaVersion(install); + for (Iterator iterator = requiredJavaVersions.iterator(); iterator.hasNext();) { + if (iterator.next().matches(javaVersion)) { + iterator.remove(); + return true; + } + } + return false; + } + + private static record RequiredJavaVersion(int from, int to) { + + public boolean matches(int version) { + return (version >= from() && version <= to()); + } + + public boolean matches(String v) { + return matches(Integer.parseInt(v)); + } + } + +} diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/jres/InstalledJREsBlock.java b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/jres/InstalledJREsBlock.java index 04ec5afac0..b0fb248e9d 100644 --- a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/jres/InstalledJREsBlock.java +++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/jres/InstalledJREsBlock.java @@ -23,7 +23,6 @@ import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; -import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.ListenerList; import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.SubMonitor; @@ -910,7 +909,7 @@ protected void search() { @Override public void run(IProgressMonitor monitor) { monitor.beginTask(JREMessages.InstalledJREsBlock_11, IProgressMonitor.UNKNOWN); - search(rootDir, locations, types, exstingLocations, monitor); + DetectVMInstallationsJob.search(rootDir, locations, types, exstingLocations, monitor); monitor.done(); } }; @@ -1031,75 +1030,6 @@ private String createUniqueId(IVMInstallType vmType) { return id; } - /** - * Searches the specified directory recursively for installed VMs, adding each - * detected VM to the found list. Any directories specified in - * the ignore are not traversed. - */ - protected void search(File directory, List found, List types, Set ignore, IProgressMonitor monitor) { - if (monitor.isCanceled()) { - return; - } - - String[] fileNames = directory.list(); - if (fileNames == null) { - return; // not a directory - } - List names = new ArrayList<>(); - names.add(null); // self - names.addAll(List.of(fileNames)); - List subDirs = new ArrayList<>(); - for (String name : names) { - if (monitor.isCanceled()) { - return; - } - File file = name == null ? directory : new File(directory, name); - monitor.subTask(NLS.bind(JREMessages.InstalledJREsBlock_14, Integer.toString(found.size()), - file.toPath().normalize().toAbsolutePath().toString().replace("&", "&&") )); // @see bug 29855 //$NON-NLS-1$ //$NON-NLS-2$ - IVMInstallType[] vmTypes = JavaRuntime.getVMInstallTypes(); - if (file.isDirectory()) { - if (ignore.add(file)) { - boolean validLocation = false; - - // Take the first VM install type that claims the location as a - // valid VM install. VM install types should be smart enough to not - // claim another type's VM, but just in case... - for (int j = 0; j < vmTypes.length; j++) { - if (monitor.isCanceled()) { - return; - } - IVMInstallType type = vmTypes[j]; - IStatus status = type.validateInstallLocation(file); - if (status.isOK()) { - String filePath = file.getPath(); - int index = filePath.lastIndexOf(File.separatorChar); - File newFile = file; - // remove bin folder from install location as java executables are found only under bin for Java 9 and above - if (index > 0 && filePath.substring(index + 1).equals("bin")) { //$NON-NLS-1$ - newFile = new File(filePath.substring(0, index)); - } - found.add(newFile); - types.add(type); - validLocation = true; - break; - } - } - if (!validLocation) { - subDirs.add(file); - } - } - } - } - while (!subDirs.isEmpty()) { - File subDir = subDirs.remove(0); - search(subDir, found, types, ignore, monitor); - if (monitor.isCanceled()) { - return; - } - } - - } - /** * Sets the checked JRE, possible null * diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/jres/JREMessages.java b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/jres/JREMessages.java index 6ba17affcc..f33c3f143a 100644 --- a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/jres/JREMessages.java +++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/jres/JREMessages.java @@ -45,7 +45,6 @@ public class JREMessages extends NLS { public static String InstalledJREsBlock_11; public static String InstalledJREsBlock_12; public static String InstalledJREsBlock_13; - public static String InstalledJREsBlock_14; public static String InstalledJREsBlock_15; public static String InstalledJREsBlock_16; diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/jres/JREMessages.properties b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/jres/JREMessages.properties index a7d387420a..0f4392b026 100644 --- a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/jres/JREMessages.properties +++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/jres/JREMessages.properties @@ -36,7 +36,6 @@ InstalledJREsBlock_10=Directory Selection InstalledJREsBlock_11=Searching... InstalledJREsBlock_12=Information InstalledJREsBlock_13=No JREs found in {0} -InstalledJREsBlock_14=Found {0} - Searching {1} InstalledJREsBlock_15=Installed &JREs: InstalledJREsBlock_16=Dupli&cate... InstalledJREsBlock_19={0} (contributed) diff --git a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/DetectVMInstallationsJob.java b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/DetectVMInstallationsJob.java index 92dc3fffcd..ba3d8568fa 100644 --- a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/DetectVMInstallationsJob.java +++ b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/DetectVMInstallationsJob.java @@ -38,6 +38,7 @@ import org.eclipse.jdt.launching.IVMInstallType; import org.eclipse.jdt.launching.JavaRuntime; import org.eclipse.jdt.launching.VMStandin; +import org.eclipse.osgi.util.NLS; /** * Lookup for VMs installed in standard or usual locations; and add the existing ones that @@ -245,6 +246,75 @@ public boolean belongsTo(Object family) { return family.equals(FAMILY); } + /** + * Searches the specified directory recursively for installed VMs, adding each + * detected VM to the found list. Any directories specified in + * the ignore are not traversed. + */ + public static void search(File directory, List found, List types, Set ignore, IProgressMonitor monitor) { + if (monitor.isCanceled()) { + return; + } + + String[] fileNames = directory.list(); + if (fileNames == null) { + return; // not a directory + } + List names = new ArrayList<>(); + names.add(null); // self + names.addAll(List.of(fileNames)); + List subDirs = new ArrayList<>(); + for (String name : names) { + if (monitor.isCanceled()) { + return; + } + File file = name == null ? directory : new File(directory, name); + monitor.subTask(NLS.bind(LaunchingMessages.SearchingJVMs, Integer.toString(found.size()), + file.toPath().normalize().toAbsolutePath().toString().replace("&", "&&") )); // @see bug 29855 //$NON-NLS-1$ //$NON-NLS-2$ + IVMInstallType[] vmTypes = JavaRuntime.getVMInstallTypes(); + if (file.isDirectory()) { + if (ignore.add(file)) { + boolean validLocation = false; + + // Take the first VM install type that claims the location as a + // valid VM install. VM install types should be smart enough to not + // claim another type's VM, but just in case... + for (int j = 0; j < vmTypes.length; j++) { + if (monitor.isCanceled()) { + return; + } + IVMInstallType type = vmTypes[j]; + IStatus status = type.validateInstallLocation(file); + if (status.isOK()) { + String filePath = file.getPath(); + int index = filePath.lastIndexOf(File.separatorChar); + File newFile = file; + // remove bin folder from install location as java executables are found only under bin for Java 9 and above + if (index > 0 && filePath.substring(index + 1).equals("bin")) { //$NON-NLS-1$ + newFile = new File(filePath.substring(0, index)); + } + found.add(newFile); + types.add(type); + validLocation = true; + break; + } + } + if (!validLocation) { + subDirs.add(file); + } + } + } + } + while (!subDirs.isEmpty()) { + File subDir = subDirs.remove(0); + search(subDir, found, types, ignore, monitor); + if (monitor.isCanceled()) { + return; + } + } + + } + public static void initialize() { if (Boolean.getBoolean(PROPERTY_DETECT_VM_INSTALLATIONS_JOB_DISABLED)) { // early exit no need to read preferences or check env variable! diff --git a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/LaunchingMessages.java b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/LaunchingMessages.java index 39eb69dd65..5f3bd3a15b 100644 --- a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/LaunchingMessages.java +++ b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/LaunchingMessages.java @@ -18,6 +18,8 @@ public class LaunchingMessages extends NLS { private static final String BUNDLE_NAME = "org.eclipse.jdt.internal.launching.LaunchingMessages";//$NON-NLS-1$ + public static String SearchingJVMs; + public static String AbstractJavaLaunchConfigurationDelegate_Java_project_not_specified_9; public static String AbstractJavaLaunchConfigurationDelegate_JRE_home_directory_for__0__does_not_exist___1__6; public static String AbstractJavaLaunchConfigurationDelegate_JRE_home_directory_not_specified_for__0__5; diff --git a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/LaunchingMessages.properties b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/LaunchingMessages.properties index e57d2184bc..2fc82e08c4 100644 --- a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/LaunchingMessages.properties +++ b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/LaunchingMessages.properties @@ -206,4 +206,5 @@ VMLogging_1=Restoring vm library location: VMLogging_2=Creating Library with Java Install path: VMLogging_3=Default Install retrieved: lookupInstalledJVMs=Detect installed JVMs -configuringJVM=Configuring installed JVM {0} \ No newline at end of file +configuringJVM=Configuring installed JVM {0} +SearchingJVMs=Found {0} - Searching {1} \ No newline at end of file diff --git a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/JavaRuntime.java b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/JavaRuntime.java index 9d919ce668..a2662c20f9 100644 --- a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/JavaRuntime.java +++ b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/JavaRuntime.java @@ -32,6 +32,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Hashtable; @@ -1230,6 +1231,10 @@ public static IRuntimeClasspathProvider getSourceLookupPathProvider(ILaunchConfi * @since 2.0 */ public static IRuntimeClasspathEntry[] resolveRuntimeClasspathEntry(IRuntimeClasspathEntry entry, ILaunchConfiguration configuration) throws CoreException { + return resolveRuntimeClasspathEntry(entry, configuration, JavaProject.NO_RELEASE); + } + + static IRuntimeClasspathEntry[] resolveRuntimeClasspathEntry(IRuntimeClasspathEntry entry, ILaunchConfiguration configuration, int runtimeJavaVersion) throws CoreException { boolean excludeTestCode = configuration.getAttribute(IJavaLaunchConfigurationConstants.ATTR_EXCLUDE_TEST_CODE, false); switch (entry.getType()) { case IRuntimeClasspathEntry.PROJECT: @@ -1244,7 +1249,7 @@ public static IRuntimeClasspathEntry[] resolveRuntimeClasspathEntry(IRuntimeClas IClasspathAttribute[] attributes = entry.getClasspathEntry().getExtraAttributes(); boolean withoutTestCode = entry.getClasspathEntry().isWithoutTestCode(); IRuntimeClasspathEntry[] entries = resolveOutputLocations(project, entry.getClasspathProperty(), attributes, excludeTestCode - || withoutTestCode); + || withoutTestCode, runtimeJavaVersion); if (entries != null) { return entries; } @@ -1373,20 +1378,35 @@ private static IRuntimeClasspathEntry[] resolveVariableEntry(IRuntimeClasspathEn * extra attributes of the original classpath entry * @param excludeTestCode * if true, output folders corresponding to test sources are excluded + * @param runtimeJavaVersion + * the java runtime version used * * @return IRuntimeClasspathEntry[] or null * @throws CoreException * if output resolution encounters a problem */ - private static IRuntimeClasspathEntry[] resolveOutputLocations(IJavaProject project, int classpathProperty, IClasspathAttribute[] attributes, boolean excludeTestCode) throws CoreException { + private static IRuntimeClasspathEntry[] resolveOutputLocations(IJavaProject project, int classpathProperty, IClasspathAttribute[] attributes, boolean excludeTestCode, int runtimeJavaVersion) throws CoreException { List nonDefault = new ArrayList<>(); + List multiRelease = new ArrayList<>(); boolean defaultUsedByNonTest = false; + IPath def = project.getOutputLocation(); if (project.exists() && project.getProject().isOpen()) { IClasspathEntry entries[] = project.getRawClasspath(); for (int i = 0; i < entries.length; i++) { IClasspathEntry classpathEntry = entries[i]; if (classpathEntry.getEntryKind() == IClasspathEntry.CPE_SOURCE) { + int release = getRelease(classpathEntry); + if (release > runtimeJavaVersion) { + // ignore entries that target a higher release + continue; + } IPath path = classpathEntry.getOutputLocation(); + if (release >= JavaProject.FIRST_MULTI_RELEASE) { + // needs special treatment! + IPath mrOutput = Objects.requireNonNullElse(path, def).append(new Path(String.format("META-INF/versions/%s", release))); //$NON-NLS-1$ + multiRelease.add(new PathWithRelease(mrOutput, release)); + continue; + } if (path != null) { if (!(excludeTestCode && classpathEntry.isTest())) { nonDefault.add(path); @@ -1400,17 +1420,24 @@ private static IRuntimeClasspathEntry[] resolveOutputLocations(IJavaProject proj } } boolean isModular = project.getOwnModuleDescription() != null; - if (nonDefault.isEmpty() && !isModular && !excludeTestCode) { + if (nonDefault.isEmpty() && multiRelease.isEmpty() && !isModular && !excludeTestCode) { // return here only if non-modular, because patch-module might be needed otherwise return null; } // add the default location if not already included - IPath def = project.getOutputLocation(); if (!excludeTestCode || defaultUsedByNonTest) { if (!nonDefault.contains(def)) { nonDefault.add(def); } } + if (!multiRelease.isEmpty()) { + // now sort and add the multi-release output locations, must be with highest release first so that such types are found before lower ones + multiRelease.sort(Comparator.comparingInt(PathWithRelease::release)); + for (PathWithRelease pathWithRelease : multiRelease) { + nonDefault.add(0, pathWithRelease.path()); + } + } + IRuntimeClasspathEntry[] locations = new IRuntimeClasspathEntry[nonDefault.size()]; for (int i = 0; i < locations.length; i++) { IClasspathEntry newEntry = JavaCore.newLibraryEntry(nonDefault.get(i), null, null, null, attributes, false); @@ -1425,6 +1452,18 @@ private static IRuntimeClasspathEntry[] resolveOutputLocations(IJavaProject proj return locations; } + private static int getRelease(IClasspathEntry classpathEntry) { + String releaseAttribute = ClasspathEntry.getExtraAttribute(classpathEntry, IClasspathAttribute.RELEASE); + if (releaseAttribute != null) { + try { + return Integer.parseInt(releaseAttribute); + } catch (RuntimeException e) { + // can't use it then! + } + } + return JavaProject.NO_RELEASE; + } + private static boolean containsModuleInfo(IRuntimeClasspathEntry entry) { return new File(entry.getLocation() + File.separator + "module-info.class").exists(); //$NON-NLS-1$ } @@ -1491,7 +1530,7 @@ public static IRuntimeClasspathEntry[] resolveRuntimeClasspathEntry(IRuntimeClas IClasspathAttribute[] attributes = entry.getClasspathEntry().getExtraAttributes(); boolean withoutTestCode = entry.getClasspathEntry().isWithoutTestCode(); IRuntimeClasspathEntry[] entries = resolveOutputLocations(jp, entry.getClasspathProperty(), attributes, excludeTestCode - || withoutTestCode); + || withoutTestCode, JavaProject.NO_RELEASE); if (entries != null) { return entries; } @@ -1671,10 +1710,11 @@ public static IRuntimeClasspathEntry[] computeUnresolvedRuntimeClasspath(ILaunch * @since 2.0 */ public static IRuntimeClasspathEntry[] resolveRuntimeClasspath(IRuntimeClasspathEntry[] entries, ILaunchConfiguration configuration) throws CoreException { + IRuntimeClasspathProvider classpathProvider = getClasspathProvider(configuration); if (!isModularConfiguration(configuration)) { - return getClasspathProvider(configuration).resolveClasspath(entries, configuration); + return classpathProvider.resolveClasspath(entries, configuration); } - IRuntimeClasspathEntry[] entries1 = getClasspathProvider(configuration).resolveClasspath(entries, configuration); + IRuntimeClasspathEntry[] entries1 = classpathProvider.resolveClasspath(entries, configuration); List entries2 = new ArrayList<>(entries1.length); IJavaProject project; try { @@ -3759,4 +3799,8 @@ private static String joinedSortedList(Collection list) { Arrays.sort(limitArray); return String.join(COMMA, limitArray); } + + private static record PathWithRelease(IPath path, int release) { + + } } diff --git a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/StandardClasspathProvider.java b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/StandardClasspathProvider.java index 03a47db8c1..e0e0f478d8 100644 --- a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/StandardClasspathProvider.java +++ b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/StandardClasspathProvider.java @@ -22,8 +22,11 @@ import java.util.Set; import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; import org.eclipse.debug.core.ILaunchConfiguration; import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.internal.core.JavaProject; +import org.eclipse.jdt.internal.launching.JREContainerInitializer; /** * Default implementation for classpath provider. @@ -90,9 +93,27 @@ public IRuntimeClasspathEntry[] computeUnresolvedClasspath(ILaunchConfiguration @Override public IRuntimeClasspathEntry[] resolveClasspath(IRuntimeClasspathEntry[] entries, ILaunchConfiguration configuration) throws CoreException { // use an ordered set to avoid duplicates + int javaRuntimeVersion = JavaProject.NO_RELEASE; Set all = new LinkedHashSet<>(entries.length); for (int i = 0; i < entries.length; i++) { - IRuntimeClasspathEntry[] resolved =JavaRuntime.resolveRuntimeClasspathEntry(entries[i], configuration); + IRuntimeClasspathEntry entry = entries[i]; + if (entry.getType() == IRuntimeClasspathEntry.CONTAINER) { + IPath path = entry.getPath(); + if (JavaRuntime.JRE_CONTAINER.equals(path.segment(0))) { + IVMInstall vm = JREContainerInitializer.resolveVM(path); + if (vm instanceof IVMInstall2 vmi2) { + try { + String javaVersion = vmi2.getJavaVersion().split("\\.")[0]; //$NON-NLS-1$ + javaRuntimeVersion = Integer.parseInt(javaVersion); + } catch (RuntimeException rte) { + // can't be used then! + } + } + } + } + } + for (int i = 0; i < entries.length; i++) { + IRuntimeClasspathEntry[] resolved = JavaRuntime.resolveRuntimeClasspathEntry(entries[i], configuration, javaRuntimeVersion); for (int j = 0; j < resolved.length; j++) { all.add(resolved[j]); }