diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 77327b5c4..6eebf537a 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -2,6 +2,12 @@ ## 2.9.2 +### Maven Execution JRE used for Tests + +Similar to what is done for the Maven Launch Actions the JRE for tests (TestNG, JUnit) is now also derived from +the Maven execution JRE. Previously this was by default set to the project's JRE (derived from the `maven-compiler-plugin`s target/release configuration). +The Maven execution JRE is determined from the `maven-enforcer-plugin`s `requireJavaVersion` rule by selecting the best matching installed JRE for the [configured Java version range](https://maven.apache.org/enforcer/enforcer-rules/requireJavaVersion.html). + ### Multi-Release-Jar support JDT added support for native [https://openjdk.org/jeps/238](Multi-Release) compilation support recently. diff --git a/org.eclipse.m2e.core.tests/src/org/eclipse/m2e/internal/launch/MavenLaunchDelegateTest.java b/org.eclipse.m2e.core.tests/src/org/eclipse/m2e/internal/launch/MavenLaunchDelegateTest.java index 901795266..c910685c3 100644 --- a/org.eclipse.m2e.core.tests/src/org/eclipse/m2e/internal/launch/MavenLaunchDelegateTest.java +++ b/org.eclipse.m2e.core.tests/src/org/eclipse/m2e/internal/launch/MavenLaunchDelegateTest.java @@ -17,10 +17,8 @@ import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.when; -import java.io.File; import java.util.List; -import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException; import org.eclipse.core.resources.IProject; import org.eclipse.core.runtime.CoreException; import org.eclipse.debug.core.DebugPlugin; @@ -47,68 +45,30 @@ public class MavenLaunchDelegateTest extends AbstractMavenProjectTestCase { private static final String DEFAULT_VM = "defaultVM"; private static final List AVAILABLE_VM_VERSIONS = List.of("17.0.4", "11.0.7", "13.0.5", "11.0.1", "1.8.0"); - @Test - public void testGetBestMatchingVM_majorOnly() throws InvalidVersionSpecificationException { - try (var mock = mockJavaRuntime()) { - assertEquals("11.0.7", MavenLaunchDelegate.getBestMatchingVM("11").getId()); - } - } - - @Test - public void testGetBestMatchingVM_rangeWithOnlyMajorLowerBound() throws InvalidVersionSpecificationException { - try (var mock = mockJavaRuntime()) { - assertEquals("11.0.7", MavenLaunchDelegate.getBestMatchingVM("[11,)").getId()); - } - } - - @Test - public void testGetBestMatchingVM_9versionRange() throws InvalidVersionSpecificationException { - try (var mock = mockJavaRuntime()) { - assertEquals("17.0.4", MavenLaunchDelegate.getBestMatchingVM("[11,18)").getId()); - } - } - - @Test - public void testGetBestMatchingVM_1XversionRange() throws InvalidVersionSpecificationException { - try (var mock = mockJavaRuntime()) { - assertEquals("1.8.0", MavenLaunchDelegate.getBestMatchingVM("[1.8,9)").getId()); - } - } - - @Test - public void testGetBestMatchingVM_versionRangeWithNoMajorVersionMatch() { - try (var mock = mockJavaRuntime()) { - assertEquals("13.0.5", MavenLaunchDelegate.getBestMatchingVM("[12,)").getId()); - } - } @Test public void testRequiredJavaVersionFromEnforcerRule_Version() throws Exception { IProject project = importProject("resources/projects/enforcerSettingsWithVersion/pom.xml"); - assertRequiredJavaBuildVersion(project, "13.0.3", "13.0.5"); + assertRequiredJavaBuildVersion(project, "13.0.5"); } @Test public void testRequiredJavaVersionFromEnforcerRule_VersionRange() throws Exception { IProject project = importProject("resources/projects/enforcerSettingsWithVersionRange/pom.xml"); - assertRequiredJavaBuildVersion(project, "[11.0.6,13)", "11.0.7"); + assertRequiredJavaBuildVersion(project, "11.0.7"); } @Test public void testRequiredJavaVersionFromEnforcerRule_NoVersionRange() throws Exception { IProject project = importProject("resources/projects/enforcerSettingsWithoutRequiredJavaVersion/pom.xml"); - assertRequiredJavaBuildVersion(project, null, DEFAULT_VM); + assertRequiredJavaBuildVersion(project, DEFAULT_VM); } - private void assertRequiredJavaBuildVersion(IProject project, String expectedVersionRange, String expectedVMVersion) + private void assertRequiredJavaBuildVersion(IProject project, String expectedVMVersion) throws Exception { waitForJobsToComplete(); - File pomFile = project.getLocation().toFile(); - - assertEquals(expectedVersionRange, MavenLaunchDelegate.readEnforcedJavaVersion(pomFile, monitor)); - String pomDir = "${workspace_loc:/" + project.getName() + "}"; try (var mock = mockJavaRuntime()) { diff --git a/org.eclipse.m2e.feature/feature.xml b/org.eclipse.m2e.feature/feature.xml index e153e1f1f..b637f8d34 100644 --- a/org.eclipse.m2e.feature/feature.xml +++ b/org.eclipse.m2e.feature/feature.xml @@ -2,7 +2,7 @@ AVAILABLE_VM_VERSIONS = List.of("17.0.4", "11.0.7", "13.0.5", "11.0.1", "1.8.0"); + + @Test + public void testGetBestMatchingVM_majorOnly() { + try (var mock = mockJavaRuntime()) { + assertEquals("11.0.7", MavenExecutionJre.getBestMatchingVM("11").get().getId()); + } + } + + @Test + public void testGetBestMatchingVM_rangeWithOnlyMajorLowerBound() { + try (var mock = mockJavaRuntime()) { + assertEquals("11.0.7", MavenExecutionJre.getBestMatchingVM("[11,)").get().getId()); + } + } + + @Test + public void testGetBestMatchingVM_9versionRange() { + try (var mock = mockJavaRuntime()) { + assertEquals("17.0.4", MavenExecutionJre.getBestMatchingVM("[11,18)").get().getId()); + } + } + + @Test + public void testGetBestMatchingVM_1XversionRange() { + try (var mock = mockJavaRuntime()) { + assertEquals("1.8.0", MavenExecutionJre.getBestMatchingVM("[1.8,9)").get().getId()); + } + } + + @Test + public void testGetBestMatchingVM_versionRangeWithNoMajorVersionMatch() { + try (var mock = mockJavaRuntime()) { + assertEquals("13.0.5", MavenExecutionJre.getBestMatchingVM("[12,)").get().getId()); + } + } + + private static MockedStatic mockJavaRuntime() { + IVMInstall defaultVM = Mockito.mock(IVMInstall.class); + Mockito.when(defaultVM.getId()).thenReturn(DEFAULT_VM); + + IVMInstallType standardVMType = Mockito.mock(StandardVMType.class, Mockito.CALLS_REAL_METHODS); + IVMInstall[] installs = AVAILABLE_VM_VERSIONS.stream().map(version -> { + AbstractVMInstall vm = Mockito.mock(AbstractVMInstall.class, Mockito.CALLS_REAL_METHODS); + when(vm.getId()).thenReturn(version); + when(vm.getJavaVersion()).thenReturn(version); + when(vm.getVMInstallType()).thenReturn(standardVMType); + when(vm.getName()).thenReturn("JDK " + version); + return vm; + }).toArray(IVMInstall[]::new); + Mockito.doReturn(installs).when(standardVMType).getVMInstalls(); + + MockedStatic javaRuntimeMock = Mockito.mockStatic(JavaRuntime.class, Mockito.CALLS_REAL_METHODS); + javaRuntimeMock.when(JavaRuntime::getVMInstallTypes).thenReturn(new IVMInstallType[] { standardVMType }); + javaRuntimeMock.when(() -> JavaRuntime.computeVMInstall(Mockito.any())).thenReturn(defaultVM); + return javaRuntimeMock; + } + +} diff --git a/org.eclipse.m2e.jdt.tests/src/org/eclipse/m2e/jdt/tests/UnitTestLaunchConfigConfigurationTest.java b/org.eclipse.m2e.jdt.tests/src/org/eclipse/m2e/jdt/tests/UnitTestLaunchConfigConfigurationTest.java index 29b76fd84..210f9ab79 100644 --- a/org.eclipse.m2e.jdt.tests/src/org/eclipse/m2e/jdt/tests/UnitTestLaunchConfigConfigurationTest.java +++ b/org.eclipse.m2e.jdt.tests/src/org/eclipse/m2e/jdt/tests/UnitTestLaunchConfigConfigurationTest.java @@ -149,7 +149,7 @@ public void test_configuration_must_be_updated_with_surefire_config() ILaunchConfiguration config = updatedConfigurations[0]; // check argLine - String argLine = config.getAttribute(UnitTestSupport.LAUNCH_CONFIG_VM_ARGUMENTS, ""); + String argLine = config.getAttribute(IJavaLaunchConfigurationConstants.ATTR_VM_ARGUMENTS, ""); assertTrue(argLine.contains("--argLineItem=surefireArgLineValue")); // check environmentVariables @@ -208,7 +208,7 @@ public void test_configuration_must_be_updated_with_failsafe_config() ILaunchConfiguration config = updatedConfigurations[0]; // check argLine - String argLine = config.getAttribute(UnitTestSupport.LAUNCH_CONFIG_VM_ARGUMENTS, ""); + String argLine = config.getAttribute(IJavaLaunchConfigurationConstants.ATTR_VM_ARGUMENTS, ""); assertTrue(argLine.contains("--argLineItem=failsafeArgLineValue")); // check environmentVariables @@ -260,7 +260,7 @@ public void test_configuration_must_be_updated_with_surefire_config_when_created ILaunchConfiguration config = updatedConfigurations[0]; // check argLine - String argLine = config.getAttribute(UnitTestSupport.LAUNCH_CONFIG_VM_ARGUMENTS, ""); + String argLine = config.getAttribute(IJavaLaunchConfigurationConstants.ATTR_VM_ARGUMENTS, ""); assertTrue(argLine.contains("--argLineItem=surefireArgLineValue")); // check environmentVariables @@ -309,7 +309,7 @@ public void test_configuration_must_be_updated_with_failSafe_config_when_created ILaunchConfiguration config = updatedConfigurations[0]; // check argLine - String argLine = config.getAttribute(UnitTestSupport.LAUNCH_CONFIG_VM_ARGUMENTS, ""); + String argLine = config.getAttribute(IJavaLaunchConfigurationConstants.ATTR_VM_ARGUMENTS, ""); assertTrue(argLine.contains("--argLineItem=failsafeArgLineValue")); // check environmentVariables @@ -353,7 +353,7 @@ public void test_deferred_variable_are_resolved() throws CoreException, IOExcept assertTrue(updatedConfigurations.length == 1); ILaunchConfiguration config = updatedConfigurations[0]; - String argLine = config.getAttribute(UnitTestSupport.LAUNCH_CONFIG_VM_ARGUMENTS, ""); + String argLine = config.getAttribute(IJavaLaunchConfigurationConstants.ATTR_VM_ARGUMENTS, ""); assertTrue(argLine.contains("-javaagent")); // resolved jacoco agent assertTrue(argLine.contains("@{titi.tata}")); // unresolved property is unchanged as in CLI } diff --git a/org.eclipse.m2e.jdt/META-INF/MANIFEST.MF b/org.eclipse.m2e.jdt/META-INF/MANIFEST.MF index 6e296b1e7..f3222241d 100644 --- a/org.eclipse.m2e.jdt/META-INF/MANIFEST.MF +++ b/org.eclipse.m2e.jdt/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %Bundle-Name Bundle-SymbolicName: org.eclipse.m2e.jdt;singleton:=true -Bundle-Version: 2.4.200.qualifier +Bundle-Version: 2.5.0.qualifier Bundle-Localization: plugin Export-Package: org.eclipse.m2e.jdt, org.eclipse.m2e.jdt.internal;x-friends:="org.eclipse.m2e.jdt.ui", diff --git a/org.eclipse.m2e.jdt/src/org/eclipse/m2e/jdt/MavenExecutionJre.java b/org.eclipse.m2e.jdt/src/org/eclipse/m2e/jdt/MavenExecutionJre.java new file mode 100644 index 000000000..7b89f9119 --- /dev/null +++ b/org.eclipse.m2e.jdt/src/org/eclipse/m2e/jdt/MavenExecutionJre.java @@ -0,0 +1,191 @@ +/******************************************************************************** + * Copyright (c) 2025, 2025 715508 and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * 715508 - initial API and implementation + ********************************************************************************/ + +package org.eclipse.m2e.jdt; + +import java.util.Comparator; +import java.util.List; +import java.util.Optional; +import java.util.SortedMap; +import java.util.TreeMap; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants; +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.apache.maven.artifact.versioning.ArtifactVersion; +import org.apache.maven.artifact.versioning.DefaultArtifactVersion; +import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException; +import org.apache.maven.artifact.versioning.VersionRange; +import org.apache.maven.plugin.MojoExecution; +import org.apache.maven.project.MavenProject; + +import org.eclipse.m2e.core.MavenPlugin; +import org.eclipse.m2e.core.project.IMavenProjectFacade; + + +/** + * Wrapper around the JRE version suitable for executing Maven steps (i.e. plugin goals or test execution). This is not + * necessarily the same as the JRE as Eclipse's project JRE (which reflects the target runtime). + */ +public class MavenExecutionJre { + + private final String executionJreVersionRange; + + private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(MavenExecutionJre.class); + + private static final String GOAL_ENFORCE = "enforce"; //$NON-NLS-1$ + + private static final String ENFORCER_PLUGIN_ARTIFACT_ID = "maven-enforcer-plugin"; //$NON-NLS-1$ + + private static final String ENFORCER_PLUGIN_GROUP_ID = "org.apache.maven.plugins"; //$NON-NLS-1$ + + private static final ArtifactVersion DEFAULT_JAVA_VERSION = new DefaultArtifactVersion("0.0.0"); //$NON-NLS-1$ + + public static Optional forProject(IMavenProjectFacade project, IProgressMonitor monitor) + throws CoreException { + Optional executionJreVersionRange = readEnforcedVersion(project, monitor); + return executionJreVersionRange.map(MavenExecutionJre::new); + } + + private MavenExecutionJre(String executionJreVersionRange) { + this.executionJreVersionRange = executionJreVersionRange; + } + + private static Optional readEnforcedVersion(IMavenProjectFacade project, IProgressMonitor monitor) + throws CoreException { + List mojoExecutions = project.getMojoExecutions(ENFORCER_PLUGIN_GROUP_ID, + ENFORCER_PLUGIN_ARTIFACT_ID, monitor, GOAL_ENFORCE); + for(MojoExecution mojoExecution : mojoExecutions) { + Optional version = getRequiredJavaVersionFromEnforcerRule(project.getMavenProject(monitor), mojoExecution, + monitor); + if(version.isPresent()) { + return version; + } + } + log.debug("No 'requireJavaVersion' rule found in maven-enforcer-plugin executions for project {}", + project.getProject().getName()); + return Optional.empty(); + } + + private static Optional getRequiredJavaVersionFromEnforcerRule(MavenProject mavenProject, + MojoExecution mojoExecution, IProgressMonitor monitor) throws CoreException { + // https://maven.apache.org/enforcer/enforcer-rules/requireJavaVersion.html + List parameter = List.of("rules", "requireJavaVersion", "version"); + @SuppressWarnings("restriction") + String version = ((org.eclipse.m2e.core.internal.embedder.MavenImpl) MavenPlugin.getMaven()) + .getMojoParameterValue(mavenProject, mojoExecution, parameter, String.class, monitor); + if(version == null) { + return Optional.empty(); + } + // normalize version (https://issues.apache.org/jira/browse/MENFORCER-440) + if("8".equals(version)) { + version = "1.8"; + } + return Optional.of(version); + } + + public String getExecutionJreVersionRange() { + return executionJreVersionRange; + } + + public Optional getBestMatchingVM() { + return getBestMatchingVM(executionJreVersionRange); + } + + /** + * The returned container path can be used as value for + * {@link IJavaLaunchConfigurationConstants.ATTR_JRE_CONTAINER_PATH}. + * + * @return the best matching JRE container path, or empty if no matching JRE could be found + */ + public Optional getBestMatchingJreContainerPath() { + return getBestMatchingVM().map(vm -> JavaRuntime.newJREContainerPath(vm).toPortableString()); + } + + public static Optional getBestMatchingVM(String requiredVersion) { + try { + VersionRange versionRange = VersionRange.createFromVersionSpec(requiredVersion); + // find all matching JVMs (sorted by version) + List matchingJREs = getAllMatchingJREs(versionRange); + if(matchingJREs.isEmpty()) { + return Optional.empty(); + } + + // for ranges with only lower bound or just a recommended version: + ArtifactVersion mainVersion; + if(versionRange.getRecommendedVersion() != null) { + mainVersion = versionRange.getRecommendedVersion(); + } else if(versionRange.getRestrictions().size() == 1 + && versionRange.getRestrictions().get(0).getUpperBound() == null) { + mainVersion = versionRange.getRestrictions().get(0).getLowerBound(); + } else { + mainVersion = null; + } + if(mainVersion != null) { + // pick nearest major version + int nearestMajorVersion = getJREVersion(matchingJREs.getLast()).getMajorVersion(); + // with highest minor/patch version + return matchingJREs.stream().filter(jre -> getJREVersion(jre).getMajorVersion() == nearestMajorVersion) + .findFirst(); + } + // otherwise use highest matching version + return Optional.of(matchingJREs.getFirst()); + } catch(InvalidVersionSpecificationException ex) { + log.warn("Invalid version range", ex); + } + return Optional.empty(); + } + + private static List getAllMatchingJREs(VersionRange versionRange) { + // find all matching JVMs and sort by their version (highest first) + SortedMap installedJREsByVersion = new TreeMap<>(Comparator.reverseOrder()); + + for(IVMInstallType vmType : JavaRuntime.getVMInstallTypes()) { + for(IVMInstall vm : vmType.getVMInstalls()) { + if(satisfiesVersionRange(vm, versionRange)) { + ArtifactVersion jreVersion = getJREVersion(vm); + if(jreVersion != DEFAULT_JAVA_VERSION) { + installedJREsByVersion.put(jreVersion, vm); + } else { + log.debug("Skipping IVMInstall '{}' from type {} as not implementing IVMInstall2", vm.getName(), + vmType.getName()); + } + } + } + } + return List.copyOf(installedJREsByVersion.values()); + } + + private static boolean satisfiesVersionRange(IVMInstall jre, VersionRange versionRange) { + ArtifactVersion jreVersion = getJREVersion(jre); + if(versionRange.getRecommendedVersion() != null) { + return jreVersion.compareTo(versionRange.getRecommendedVersion()) >= 0; + } + return versionRange.containsVersion(jreVersion); + } + + private static ArtifactVersion getJREVersion(IVMInstall jre) { + if(jre instanceof IVMInstall2 jre2) { + String version = jre2.getJavaVersion(); + if(version != null) { + return new DefaultArtifactVersion(version); + } + } + return DEFAULT_JAVA_VERSION; + } +} diff --git a/org.eclipse.m2e.jdt/src/org/eclipse/m2e/jdt/internal/UnitTestSupport.java b/org.eclipse.m2e.jdt/src/org/eclipse/m2e/jdt/internal/UnitTestSupport.java index 1be57c009..698d41d3a 100644 --- a/org.eclipse.m2e.jdt/src/org/eclipse/m2e/jdt/internal/UnitTestSupport.java +++ b/org.eclipse.m2e.jdt/src/org/eclipse/m2e/jdt/internal/UnitTestSupport.java @@ -41,6 +41,7 @@ import org.eclipse.debug.core.ILaunchConfigurationType; import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy; import org.eclipse.debug.core.ILaunchManager; +import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants; import org.eclipse.jdt.launching.JavaRuntime; import org.apache.maven.plugin.Mojo; @@ -50,6 +51,7 @@ import org.eclipse.m2e.core.MavenPlugin; import org.eclipse.m2e.core.embedder.IMaven; import org.eclipse.m2e.core.project.IMavenProjectFacade; +import org.eclipse.m2e.jdt.MavenExecutionJre; import org.eclipse.m2e.jdt.internal.launch.MavenRuntimeClasspathProvider; @@ -76,31 +78,11 @@ public class UnitTestSupport { */ private static final String GET_INCLUDED_AND_EXCLUDED_TESTS_METHOD = "getIncludedAndExcludedTests"; - /** - * Launch configuration attribute for the main class - */ - private static final String LAUNCH_CONFIG_MAIN_CLASS = "org.eclipse.jdt.launching.MAIN_TYPE"; - - /** - * Launch configuration attribute for the project - */ - private static final String LAUNCH_CONFIG_PROJECT = "org.eclipse.jdt.launching.PROJECT_ATTR"; - - /** - * Launch configuration attribute for the VM arguments - */ - public static final String LAUNCH_CONFIG_VM_ARGUMENTS = "org.eclipse.jdt.launching.VM_ARGUMENTS"; - /** * Launch configuration attribute for the environment variables */ public static final String LAUNCH_CONFIG_ENVIRONMENT_VARIABLES = "org.eclipse.debug.core.environmentVariables"; - /** - * Launch configuration attribute for the system properties - */ - private static final String LAUNCH_CONFIG_WORKING_DIRECTORY = "org.eclipse.jdt.launching.WORKING_DIRECTORY"; - /** * Launch configuration attribute for the working directory */ @@ -214,7 +196,8 @@ public void setupLaunchConfigurations(IProject project) { ILaunchConfiguration[] configurations = launchManager.getLaunchConfigurations(type); for(ILaunchConfiguration configuration : configurations) { // Check if the configuration is associated with the desired project and type - String configurationProjectName = configuration.getAttribute(LAUNCH_CONFIG_PROJECT, ""); + String configurationProjectName = configuration + .getAttribute(IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME, ""); if(project.getName().equals(configurationProjectName)) { LOG.info("Reset launch configuration name: {}", configuration.getName()); setupLaunchConfiguration(configuration); @@ -252,7 +235,7 @@ public void setupLaunchConfiguration(ILaunchConfiguration configuration) { // update the configuration only if arg extraction was successfull if(args != null) { - defineConfigurationValues(project, configuration, args); + defineConfigurationValues(project, configuration, args, MavenExecutionJre.forProject(facade, null)); } } @@ -267,7 +250,7 @@ public void setupLaunchConfiguration(ILaunchConfiguration configuration) { * Define the configuration values */ private void defineConfigurationValues(IProject project, ILaunchConfiguration configuration, - TestLaunchArguments args) throws CoreException { + TestLaunchArguments args, Optional mavenExecutionJre) throws CoreException { ILaunchConfigurationWorkingCopy copy = configuration.getWorkingCopy(); @@ -283,14 +266,15 @@ private void defineConfigurationValues(IProject project, ILaunchConfiguration co .filter(e -> e.getKey() != null && e.getValue() != null) .forEach(e -> launchArguments.add("-D" + e.getKey() + "=" + escapeValue(e.getValue()))); } - copy.setAttribute(LAUNCH_CONFIG_VM_ARGUMENTS, launchArguments.toString()); + copy.setAttribute(IJavaLaunchConfigurationConstants.ATTR_VM_ARGUMENTS, launchArguments.toString()); try { if(args.workingDirectory() != null && !Files.isSameFile(project.getLocation().toPath().toAbsolutePath(), args.workingDirectory().toPath())) { - copy.setAttribute(LAUNCH_CONFIG_WORKING_DIRECTORY, args.workingDirectory().getAbsolutePath()); + copy.setAttribute(IJavaLaunchConfigurationConstants.ATTR_WORKING_DIRECTORY, + args.workingDirectory().getAbsolutePath()); } else { - copy.setAttribute(LAUNCH_CONFIG_WORKING_DIRECTORY, (String) null); + copy.setAttribute(IJavaLaunchConfigurationConstants.ATTR_WORKING_DIRECTORY, (String) null); } } catch(IOException ex) { LOG.error(ex.getMessage(), ex); @@ -303,6 +287,11 @@ private void defineConfigurationValues(IProject project, ILaunchConfiguration co copy.setAttribute(LAUNCH_CONFIG_ENVIRONMENT_VARIABLES, filteredMap); } + // set the execution JRE if any + mavenExecutionJre.ifPresent(jre -> { + jre.getBestMatchingJreContainerPath() + .ifPresent(jreId -> copy.setAttribute(IJavaLaunchConfigurationConstants.ATTR_JRE_CONTAINER_PATH, jreId)); + }); copy.doSave(); } @@ -316,7 +305,7 @@ private static String escapeValue(String raw) { private TestLaunchArguments getTestLaunchArguments(ILaunchConfiguration configuration, IMavenProjectFacade facade, IProgressMonitor monitor) throws CoreException { - String testClass = configuration.getAttribute(LAUNCH_CONFIG_MAIN_CLASS, ""); + String testClass = configuration.getAttribute(IJavaLaunchConfigurationConstants.ATTR_MAIN_TYPE_NAME, ""); MavenProject mavenProject = facade.getMavenProject(); // find test executions diff --git a/org.eclipse.m2e.launching/META-INF/MANIFEST.MF b/org.eclipse.m2e.launching/META-INF/MANIFEST.MF index bae5037b2..0bf18d117 100644 --- a/org.eclipse.m2e.launching/META-INF/MANIFEST.MF +++ b/org.eclipse.m2e.launching/META-INF/MANIFEST.MF @@ -18,6 +18,7 @@ Require-Bundle: org.eclipse.core.runtime;bundle-version="[3.27.0,4.0.0)", org.eclipse.m2e.maven.runtime;bundle-version="[3.8.6,4.0.0)", org.eclipse.m2e.core;bundle-version="[2.0.0,3.0.0)", org.eclipse.m2e.core.ui;bundle-version="[2.0.0,3.0.0)", + org.eclipse.m2e.jdt;bundle-version="[2.5.0,3.0.0)", org.eclipse.m2e.workspace.cli;bundle-version="0.1.0" Bundle-ActivationPolicy: lazy Bundle-RequiredExecutionEnvironment: JavaSE-21 diff --git a/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/MavenLaunchDelegate.java b/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/MavenLaunchDelegate.java index 7a282d64f..366060f0c 100644 --- a/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/MavenLaunchDelegate.java +++ b/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/MavenLaunchDelegate.java @@ -20,11 +20,8 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.util.Collections; -import java.util.Comparator; import java.util.List; import java.util.Optional; -import java.util.SortedMap; -import java.util.TreeMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -34,10 +31,8 @@ import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; -import org.eclipse.core.runtime.ILog; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; -import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.preferences.IPreferencesService; @@ -47,19 +42,13 @@ import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants; import org.eclipse.jdt.launching.IVMInstall; -import org.eclipse.jdt.launching.IVMInstall2; -import org.eclipse.jdt.launching.IVMInstallType; import org.eclipse.jdt.launching.IVMRunner; import org.eclipse.jdt.launching.JavaLaunchDelegate; -import org.eclipse.jdt.launching.JavaRuntime; import org.eclipse.osgi.util.NLS; -import org.apache.maven.artifact.versioning.ArtifactVersion; import org.apache.maven.artifact.versioning.DefaultArtifactVersion; import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException; import org.apache.maven.artifact.versioning.VersionRange; -import org.apache.maven.plugin.MojoExecution; -import org.apache.maven.project.MavenProject; import org.eclipse.m2e.actions.MavenLaunchConstants; import org.eclipse.m2e.core.MavenPlugin; @@ -69,13 +58,12 @@ import org.eclipse.m2e.core.project.IMavenProjectFacade; import org.eclipse.m2e.core.project.IMavenProjectRegistry; import org.eclipse.m2e.internal.launch.MavenRuntimeLaunchSupport.VMArguments; +import org.eclipse.m2e.jdt.MavenExecutionJre; public class MavenLaunchDelegate extends JavaLaunchDelegate implements MavenLaunchConstants { private static final Logger log = LoggerFactory.getLogger(MavenLaunchDelegate.class); - private static final ILog ECLIPSE_LOG = Platform.getLog(MavenLaunchDelegate.class); - private static final String LAUNCHER_TYPE = "org.codehaus.classworlds.Launcher"; //$NON-NLS-1$ //classworlds 2.0 @@ -221,13 +209,17 @@ public IVMInstall getVMInstall(ILaunchConfiguration configuration) throws CoreEx // use the Maven JVM if nothing explicitly configured in the launch configuration File pomDirectory = getPomDirectory(configuration); if(!configuration.hasAttribute(IJavaLaunchConfigurationConstants.ATTR_JRE_CONTAINER_PATH)) { - String requiredJavaVersion = readEnforcedJavaVersion(pomDirectory, monitor); - if(requiredJavaVersion != null) { - IVMInstall jre = getBestMatchingVM(requiredJavaVersion); - if(jre != null) { - return jre; + Optional mavenProject = getMavenProject(pomDirectory, monitor); + if(mavenProject.isPresent()) { + log.debug("Looking for enforced JRE for Maven project {}", mavenProject.get().getProject().getName()); + Optional jre = MavenExecutionJre.forProject(mavenProject.get(), monitor) + .flatMap(MavenExecutionJre::getBestMatchingVM); + if(jre.isPresent()) { + log.info("Run with Maven execution JRE: {}", jre.get()); + return jre.get(); } } + // if we end here, then no specific JRE was enforced, fall back to default = Project JRE } Optional project = getContainer(pomDirectory).map(IContainer::getProject) .filter(p -> JavaCore.create(p).exists()); @@ -240,140 +232,17 @@ public IVMInstall getVMInstall(ILaunchConfiguration configuration) throws CoreEx return super.getVMInstall(configuration); } - public static String readEnforcedJavaVersion(File pomDirectory, IProgressMonitor monitor) { - try { - Optional container = getContainer(pomDirectory); - if(container.isPresent()) { - IPath pomPath = IPath.fromOSString(IMavenConstants.POM_FILE_NAME); - if(container.get().exists(pomPath)) { - IFile pomFile = container.get().getFile(pomPath); - IMavenProjectRegistry projectManager = MavenPlugin.getMavenProjectRegistry(); - IMavenProjectFacade mavenProject = projectManager.create(pomFile, true, new NullProgressMonitor()); - if(mavenProject != null) { - return readEnforcedVersion(mavenProject, monitor); - } - } - } - //TODO: handle the case if the pomDirectory points to a project not in the workspace. Then load the bare project. - } catch(CoreException ex) { - logEnforcedJavaVersionCalculationError(ex); - } - return null; - } - - private static final String GOAL_ENFORCE = "enforce"; //$NON-NLS-1$ - - private static final String ENFORCER_PLUGIN_ARTIFACT_ID = "maven-enforcer-plugin"; //$NON-NLS-1$ - - private static final String ENFORCER_PLUGIN_GROUP_ID = "org.apache.maven.plugins"; //$NON-NLS-1$ - - private static String readEnforcedVersion(IMavenProjectFacade project, IProgressMonitor monitor) - throws CoreException { - List mojoExecutions = project.getMojoExecutions(ENFORCER_PLUGIN_GROUP_ID, - ENFORCER_PLUGIN_ARTIFACT_ID, monitor, GOAL_ENFORCE); - for(MojoExecution mojoExecution : mojoExecutions) { - String version = getRequiredJavaVersionFromEnforcerRule(project.getMavenProject(monitor), mojoExecution, monitor); - if(version != null) { - return version; - } - } - return null; - } - - private static String getRequiredJavaVersionFromEnforcerRule(MavenProject mavenProject, MojoExecution mojoExecution, - IProgressMonitor monitor) throws CoreException { - // https://maven.apache.org/enforcer/enforcer-rules/requireJavaVersion.html - List parameter = List.of("rules", "requireJavaVersion", "version"); - @SuppressWarnings("restriction") - String version = ((org.eclipse.m2e.core.internal.embedder.MavenImpl) MavenPlugin.getMaven()) - .getMojoParameterValue(mavenProject, mojoExecution, parameter, String.class, monitor); - if(version == null) { - return null; - } - // normalize version (https://issues.apache.org/jira/browse/MENFORCER-440) - if("8".equals(version)) { - version = "1.8"; - } - return version; - } - - private static void logEnforcedJavaVersionCalculationError(Throwable e) { - ECLIPSE_LOG.error( - "Failed to determine required Java version from maven-enforcer-plugin configuration, assuming default", e); - } - - public static IVMInstall getBestMatchingVM(String requiredVersion) { - try { - VersionRange versionRange = VersionRange.createFromVersionSpec(requiredVersion); - // find all matching JVMs (sorted by version) - List matchingJREs = getAllMatchingJREs(versionRange); - if(matchingJREs.isEmpty()) { - return null; - } - - // for ranges with only lower bound or just a recommended version: - ArtifactVersion mainVersion; - if(versionRange.getRecommendedVersion() != null) { - mainVersion = versionRange.getRecommendedVersion(); - } else if(versionRange.getRestrictions().size() == 1 - && versionRange.getRestrictions().get(0).getUpperBound() == null) { - mainVersion = versionRange.getRestrictions().get(0).getLowerBound(); - } else { - mainVersion = null; - } - if(mainVersion != null) { - // pick nearest major version - int nearestMajorVersion = getJREVersion(matchingJREs.getLast()).getMajorVersion(); - // with highest minor/patch version - return matchingJREs.stream().filter(jre -> getJREVersion(jre).getMajorVersion() == nearestMajorVersion) - .findFirst().orElse(null); - } - // otherwise use highest matching version - return matchingJREs.getFirst(); - } catch(InvalidVersionSpecificationException ex) { - log.warn("Invalid version range", ex); - } - return null; - } - - private static List getAllMatchingJREs(VersionRange versionRange) { - // find all matching JVMs and sort by their version (highest first) - SortedMap installedJREsByVersion = new TreeMap<>(Comparator.reverseOrder()); - - for(IVMInstallType vmType : JavaRuntime.getVMInstallTypes()) { - for(IVMInstall vm : vmType.getVMInstalls()) { - if(satisfiesVersionRange(vm, versionRange)) { - ArtifactVersion jreVersion = getJREVersion(vm); - if(jreVersion != DEFAULT_JAVA_VERSION) { - installedJREsByVersion.put(jreVersion, vm); - } else { - log.debug("Skipping IVMInstall '{}' from type {} as not implementing IVMInstall2", vm.getName(), - vmType.getName()); - } - } - } - } - return List.copyOf(installedJREsByVersion.values()); - } - - private static boolean satisfiesVersionRange(IVMInstall jre, VersionRange versionRange) { - ArtifactVersion jreVersion = getJREVersion(jre); - if(versionRange.getRecommendedVersion() != null) { - return jreVersion.compareTo(versionRange.getRecommendedVersion()) >= 0; - } - return versionRange.containsVersion(jreVersion); - } - - private static final ArtifactVersion DEFAULT_JAVA_VERSION = new DefaultArtifactVersion("0.0.0"); - - private static ArtifactVersion getJREVersion(IVMInstall jre) { - if(jre instanceof IVMInstall2 jre2) { - String version = jre2.getJavaVersion(); - if(version != null) { - return new DefaultArtifactVersion(version); + public static Optional getMavenProject(File pomDirectory, IProgressMonitor monitor) { + Optional container = getContainer(pomDirectory); + if(container.isPresent()) { + IPath pomPath = IPath.fromOSString(IMavenConstants.POM_FILE_NAME); + if(container.get().exists(pomPath)) { + IFile pomFile = container.get().getFile(pomPath); + IMavenProjectRegistry projectManager = MavenPlugin.getMavenProjectRegistry(); + return Optional.of(projectManager.create(pomFile, true, monitor)); } } - return DEFAULT_JAVA_VERSION; + return Optional.empty(); } @Override diff --git a/org.eclipse.m2e.launching/src/org/eclipse/m2e/ui/internal/launch/MavenJRETab.java b/org.eclipse.m2e.launching/src/org/eclipse/m2e/ui/internal/launch/MavenJRETab.java index 6f11e8994..f09ab3681 100644 --- a/org.eclipse.m2e.launching/src/org/eclipse/m2e/ui/internal/launch/MavenJRETab.java +++ b/org.eclipse.m2e.launching/src/org/eclipse/m2e/ui/internal/launch/MavenJRETab.java @@ -14,10 +14,14 @@ package org.eclipse.m2e.ui.internal.launch; import java.io.File; +import java.util.Optional; import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IProject; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.debug.core.ILaunchConfiguration; import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy; import org.eclipse.debug.ui.ILaunchConfigurationDialog; @@ -33,13 +37,17 @@ import org.eclipse.swt.layout.GridData; import org.eclipse.swt.widgets.Composite; +import org.eclipse.m2e.core.project.IMavenProjectFacade; import org.eclipse.m2e.internal.launch.MavenLaunchDelegate; import org.eclipse.m2e.internal.launch.Messages; +import org.eclipse.m2e.jdt.MavenExecutionJre; @SuppressWarnings("restriction") public class MavenJRETab extends JavaJRETab { + private static final org.slf4j.Logger LOGGER = org.slf4j.LoggerFactory.getLogger(MavenJRETab.class); + private final VMArgumentsBlock vmArgumentsBlock = new VMArgumentsBlock(); @Override @@ -70,19 +78,33 @@ protected IJavaProject getJavaProject() { @Override protected JREDescriptor getDefaultJREDescriptor() { File pomDirectory = MavenLaunchDelegate.getPomDirectory(getLaunchConfiguration()); - String version = MavenLaunchDelegate.readEnforcedJavaVersion(pomDirectory, null); - IVMInstall mavenJre = version != null ? MavenLaunchDelegate.getBestMatchingVM(version) : null; - String details; - if(mavenJre != null) { // add link - details = NLS.bind(Messages.MavenJRETab_lblDefaultDetailsRequiredJavaVersion, mavenJre.getName(), version); - } else { + IProgressMonitor monitor = new NullProgressMonitor(); + String details = null; + Optional mavenProjectFacade = MavenLaunchDelegate.getMavenProject(pomDirectory, monitor); + if(mavenProjectFacade.isPresent()) { + Optional mavenExecutionJre; + try { + mavenExecutionJre = MavenExecutionJre.forProject(mavenProjectFacade.get(), monitor); + Optional mavenJre = mavenExecutionJre.flatMap(jre -> jre.getBestMatchingVM()); + + if(mavenJre.isPresent()) { // add link + details = NLS.bind(Messages.MavenJRETab_lblDefaultDetailsRequiredJavaVersion, mavenJre.get().getName(), + mavenExecutionJre.get().getExecutionJreVersionRange()); + } + } catch(CoreException ex) { + LOGGER.warn("Unable to determine Maven JRE for project " + mavenProjectFacade.get().getProject().getName(), ex); + } + + } + if(details == null) { // TODO: add logic for getting the underlying project then fall back to default details = super.getDefaultJREDescriptor().getDescription(); } + String finalDetails = details; return new JREDescriptor() { @Override public String getDescription() { - return NLS.bind(Messages.MavenJRETab_lblDefault, details); + return NLS.bind(Messages.MavenJRETab_lblDefault, finalDetails); } }; } diff --git a/org.eclipse.m2e.sdk.feature/feature.xml b/org.eclipse.m2e.sdk.feature/feature.xml index 97dca0c32..a19f245cd 100644 --- a/org.eclipse.m2e.sdk.feature/feature.xml +++ b/org.eclipse.m2e.sdk.feature/feature.xml @@ -2,7 +2,7 @@