Skip to content

Commit c2fef3e

Browse files
committed
Support launching Multi-Release-Compiled Projects
Currently when one launches a multi-release compiled project one always gets the type from the default project folder. This now first checks what JRE is used to launch, then adds all valid folders in reverse version order to the classpath to emulate the behavior of loading a multi-release jar at runtime. Fix https://github.com/eclipse-jdt/eclipse.jdt.core/issues/4276
1 parent 9efbd89 commit c2fef3e

File tree

2 files changed

+73
-8
lines changed

2 files changed

+73
-8
lines changed

org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/JavaRuntime.java

Lines changed: 51 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import java.util.Arrays;
3333
import java.util.Collection;
3434
import java.util.Collections;
35+
import java.util.Comparator;
3536
import java.util.HashMap;
3637
import java.util.HashSet;
3738
import java.util.Hashtable;
@@ -1230,6 +1231,10 @@ public static IRuntimeClasspathProvider getSourceLookupPathProvider(ILaunchConfi
12301231
* @since 2.0
12311232
*/
12321233
public static IRuntimeClasspathEntry[] resolveRuntimeClasspathEntry(IRuntimeClasspathEntry entry, ILaunchConfiguration configuration) throws CoreException {
1234+
return resolveRuntimeClasspathEntry(entry, configuration, JavaProject.NO_RELEASE);
1235+
}
1236+
1237+
static IRuntimeClasspathEntry[] resolveRuntimeClasspathEntry(IRuntimeClasspathEntry entry, ILaunchConfiguration configuration, int runtimeJavaVersion) throws CoreException {
12331238
boolean excludeTestCode = configuration.getAttribute(IJavaLaunchConfigurationConstants.ATTR_EXCLUDE_TEST_CODE, false);
12341239
switch (entry.getType()) {
12351240
case IRuntimeClasspathEntry.PROJECT:
@@ -1244,7 +1249,7 @@ public static IRuntimeClasspathEntry[] resolveRuntimeClasspathEntry(IRuntimeClas
12441249
IClasspathAttribute[] attributes = entry.getClasspathEntry().getExtraAttributes();
12451250
boolean withoutTestCode = entry.getClasspathEntry().isWithoutTestCode();
12461251
IRuntimeClasspathEntry[] entries = resolveOutputLocations(project, entry.getClasspathProperty(), attributes, excludeTestCode
1247-
|| withoutTestCode);
1252+
|| withoutTestCode, runtimeJavaVersion);
12481253
if (entries != null) {
12491254
return entries;
12501255
}
@@ -1373,20 +1378,35 @@ private static IRuntimeClasspathEntry[] resolveVariableEntry(IRuntimeClasspathEn
13731378
* extra attributes of the original classpath entry
13741379
* @param excludeTestCode
13751380
* if true, output folders corresponding to test sources are excluded
1381+
* @param runtimeJavaVersion
1382+
* the java runtime version used
13761383
*
13771384
* @return IRuntimeClasspathEntry[] or <code>null</code>
13781385
* @throws CoreException
13791386
* if output resolution encounters a problem
13801387
*/
1381-
private static IRuntimeClasspathEntry[] resolveOutputLocations(IJavaProject project, int classpathProperty, IClasspathAttribute[] attributes, boolean excludeTestCode) throws CoreException {
1388+
private static IRuntimeClasspathEntry[] resolveOutputLocations(IJavaProject project, int classpathProperty, IClasspathAttribute[] attributes, boolean excludeTestCode, int runtimeJavaVersion) throws CoreException {
13821389
List<IPath> nonDefault = new ArrayList<>();
1390+
List<PathWithRelease> multiRelease = new ArrayList<>();
13831391
boolean defaultUsedByNonTest = false;
1392+
IPath def = project.getOutputLocation();
13841393
if (project.exists() && project.getProject().isOpen()) {
13851394
IClasspathEntry entries[] = project.getRawClasspath();
13861395
for (int i = 0; i < entries.length; i++) {
13871396
IClasspathEntry classpathEntry = entries[i];
13881397
if (classpathEntry.getEntryKind() == IClasspathEntry.CPE_SOURCE) {
1398+
int release = getRelease(classpathEntry);
1399+
if (release > runtimeJavaVersion) {
1400+
// ignore entries that target a higher release
1401+
continue;
1402+
}
13891403
IPath path = classpathEntry.getOutputLocation();
1404+
if (release >= JavaProject.FIRST_MULTI_RELEASE) {
1405+
// needs special treatment!
1406+
IPath mrOutput = Objects.requireNonNullElse(path, def).append(new Path(String.format("META-INF/versions/%s", release))); //$NON-NLS-1$
1407+
multiRelease.add(new PathWithRelease(mrOutput, release));
1408+
continue;
1409+
}
13901410
if (path != null) {
13911411
if (!(excludeTestCode && classpathEntry.isTest())) {
13921412
nonDefault.add(path);
@@ -1400,17 +1420,24 @@ private static IRuntimeClasspathEntry[] resolveOutputLocations(IJavaProject proj
14001420
}
14011421
}
14021422
boolean isModular = project.getOwnModuleDescription() != null;
1403-
if (nonDefault.isEmpty() && !isModular && !excludeTestCode) {
1423+
if (nonDefault.isEmpty() && multiRelease.isEmpty() && !isModular && !excludeTestCode) {
14041424
// return here only if non-modular, because patch-module might be needed otherwise
14051425
return null;
14061426
}
14071427
// add the default location if not already included
1408-
IPath def = project.getOutputLocation();
14091428
if (!excludeTestCode || defaultUsedByNonTest) {
14101429
if (!nonDefault.contains(def)) {
14111430
nonDefault.add(def);
14121431
}
14131432
}
1433+
if (!multiRelease.isEmpty()) {
1434+
// now sort and add the multi-release output locations, must be with highest release first so that such types are found before lower ones
1435+
multiRelease.sort(Comparator.comparingInt(PathWithRelease::release));
1436+
for (PathWithRelease pathWithRelease : multiRelease) {
1437+
nonDefault.add(0, pathWithRelease.path());
1438+
}
1439+
}
1440+
14141441
IRuntimeClasspathEntry[] locations = new IRuntimeClasspathEntry[nonDefault.size()];
14151442
for (int i = 0; i < locations.length; i++) {
14161443
IClasspathEntry newEntry = JavaCore.newLibraryEntry(nonDefault.get(i), null, null, null, attributes, false);
@@ -1425,6 +1452,18 @@ private static IRuntimeClasspathEntry[] resolveOutputLocations(IJavaProject proj
14251452
return locations;
14261453
}
14271454

1455+
private static int getRelease(IClasspathEntry classpathEntry) {
1456+
String releaseAttribute = ClasspathEntry.getExtraAttribute(classpathEntry, IClasspathAttribute.RELEASE);
1457+
if (releaseAttribute != null) {
1458+
try {
1459+
return Integer.parseInt(releaseAttribute);
1460+
} catch (RuntimeException e) {
1461+
// can't use it then!
1462+
}
1463+
}
1464+
return JavaProject.NO_RELEASE;
1465+
}
1466+
14281467
private static boolean containsModuleInfo(IRuntimeClasspathEntry entry) {
14291468
return new File(entry.getLocation() + File.separator + "module-info.class").exists(); //$NON-NLS-1$
14301469
}
@@ -1491,7 +1530,7 @@ public static IRuntimeClasspathEntry[] resolveRuntimeClasspathEntry(IRuntimeClas
14911530
IClasspathAttribute[] attributes = entry.getClasspathEntry().getExtraAttributes();
14921531
boolean withoutTestCode = entry.getClasspathEntry().isWithoutTestCode();
14931532
IRuntimeClasspathEntry[] entries = resolveOutputLocations(jp, entry.getClasspathProperty(), attributes, excludeTestCode
1494-
|| withoutTestCode);
1533+
|| withoutTestCode, JavaProject.NO_RELEASE);
14951534
if (entries != null) {
14961535
return entries;
14971536
}
@@ -1671,10 +1710,11 @@ public static IRuntimeClasspathEntry[] computeUnresolvedRuntimeClasspath(ILaunch
16711710
* @since 2.0
16721711
*/
16731712
public static IRuntimeClasspathEntry[] resolveRuntimeClasspath(IRuntimeClasspathEntry[] entries, ILaunchConfiguration configuration) throws CoreException {
1713+
IRuntimeClasspathProvider classpathProvider = getClasspathProvider(configuration);
16741714
if (!isModularConfiguration(configuration)) {
1675-
return getClasspathProvider(configuration).resolveClasspath(entries, configuration);
1715+
return classpathProvider.resolveClasspath(entries, configuration);
16761716
}
1677-
IRuntimeClasspathEntry[] entries1 = getClasspathProvider(configuration).resolveClasspath(entries, configuration);
1717+
IRuntimeClasspathEntry[] entries1 = classpathProvider.resolveClasspath(entries, configuration);
16781718
List<IRuntimeClasspathEntry> entries2 = new ArrayList<>(entries1.length);
16791719
IJavaProject project;
16801720
try {
@@ -3759,4 +3799,8 @@ private static String joinedSortedList(Collection<String> list) {
37593799
Arrays.sort(limitArray);
37603800
return String.join(COMMA, limitArray);
37613801
}
3802+
3803+
private static record PathWithRelease(IPath path, int release) {
3804+
3805+
}
37623806
}

org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/StandardClasspathProvider.java

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,11 @@
2222
import java.util.Set;
2323

2424
import org.eclipse.core.runtime.CoreException;
25+
import org.eclipse.core.runtime.IPath;
2526
import org.eclipse.debug.core.ILaunchConfiguration;
2627
import org.eclipse.jdt.core.IJavaProject;
28+
import org.eclipse.jdt.internal.core.JavaProject;
29+
import org.eclipse.jdt.internal.launching.JREContainerInitializer;
2730

2831
/**
2932
* Default implementation for classpath provider.
@@ -90,9 +93,27 @@ public IRuntimeClasspathEntry[] computeUnresolvedClasspath(ILaunchConfiguration
9093
@Override
9194
public IRuntimeClasspathEntry[] resolveClasspath(IRuntimeClasspathEntry[] entries, ILaunchConfiguration configuration) throws CoreException {
9295
// use an ordered set to avoid duplicates
96+
int javaRuntime = JavaProject.NO_RELEASE;
9397
Set<IRuntimeClasspathEntry> all = new LinkedHashSet<>(entries.length);
9498
for (int i = 0; i < entries.length; i++) {
95-
IRuntimeClasspathEntry[] resolved =JavaRuntime.resolveRuntimeClasspathEntry(entries[i], configuration);
99+
IRuntimeClasspathEntry entry = entries[i];
100+
if (entry.getType() == IRuntimeClasspathEntry.CONTAINER) {
101+
IPath path = entry.getPath();
102+
if (JavaRuntime.JRE_CONTAINER.equals(path.segment(0))) {
103+
IVMInstall vm = JREContainerInitializer.resolveVM(path);
104+
if (vm instanceof IVMInstall2 vmi2) {
105+
try {
106+
String javaVersion = vmi2.getJavaVersion().split("\\.")[0]; //$NON-NLS-1$
107+
javaRuntime = Integer.parseInt(javaVersion);
108+
} catch (RuntimeException rte) {
109+
// can't be used then!
110+
}
111+
}
112+
}
113+
}
114+
}
115+
for (int i = 0; i < entries.length; i++) {
116+
IRuntimeClasspathEntry[] resolved = JavaRuntime.resolveRuntimeClasspathEntry(entries[i], configuration, javaRuntime);
96117
for (int j = 0; j < resolved.length; j++) {
97118
all.add(resolved[j]);
98119
}

0 commit comments

Comments
 (0)