diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 8c6b64c71a..b768969b07 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -2,6 +2,8 @@ ## 2.2.0 +* 📅 Release Date: _expected_ end of Februar 2023 + ### Mojos without a mapping are now executed by default in incremental builds Before it was neccesary to explicitly configure a mapping or there is a connector or the plugin iself contains mappings for a mojo to participate in the incremental maven build. @@ -23,9 +25,21 @@ The property "true" and the "skip" property i account by M2E in the sense that, if enabled, M2E ignores the corresponding folder, which no longer appear in the Package Explorer as "Java" folders but as standard folders. This allows, depending on the need (especially compilation time), to either not compile tests or not copy test resources. -In general it is not recomended to use the mentioned properties but to use `-DskipTests` instead: +In general it is not recommended to use the mentioned properties but to use `-DskipTests` instead: https://maven.apache.org/surefire/maven-surefire-plugin/examples/skipping-tests.html + +### Configuration of Maven Execution JRE + +In the past the project's build JRE was also used by default to execute Maven itself. +Now the default Java version for executing Maven is determined from the configuration of the `maven-enforcer-plugin` rule [`requireJavaVersion`](https://maven.apache.org/enforcer/enforcer-rules/requireJavaVersion.html) when creating or updating the Maven configuration. This value is no longer considered for configuring the project's build JRE. +In case this plugin configuration is not found one falls back to either project's build JRE or workspace default JRE. + +For each Maven build configuration you can overwrite the default execution JRE in the Maven Launch configuration's JRE tab: + +![Maven Launch Configuration JRE Tab](https://user-images.githubusercontent.com/185025/208966517-7d847058-23b9-4e2e-8b1a-7a86df4836bd.png) + + ## 2.1.0 * 📅 Release Date: November 24th 2022 diff --git a/org.eclipse.m2e.core.tests/META-INF/MANIFEST.MF b/org.eclipse.m2e.core.tests/META-INF/MANIFEST.MF index 6b14c50a61..60603f825e 100644 --- a/org.eclipse.m2e.core.tests/META-INF/MANIFEST.MF +++ b/org.eclipse.m2e.core.tests/META-INF/MANIFEST.MF @@ -7,11 +7,14 @@ Bundle-Version: 2.0.0.qualifier Bundle-RequiredExecutionEnvironment: JavaSE-17 Bundle-Vendor: Eclipse.org - m2e Require-Bundle: org.eclipse.m2e.tests.common, - org.junit, - org.eclipse.core.resources, org.eclipse.core.runtime, - org.eclipse.m2e.maven.runtime -Import-Package: javax.annotation;version="1.2.0", - org.apache.commons.io + org.eclipse.m2e.launching, + org.eclipse.jdt.launching, + org.eclipse.debug.core, + org.eclipse.jdt.core +Import-Package: org.apache.commons.io, + org.junit, + org.mockito, + org.mockito.stubbing Eclipse-BundleShape: dir Automatic-Module-Name: org.eclipse.m2e.core.tests diff --git a/org.eclipse.m2e.jdt.tests/projects/enforcerSettingsWithVersion/pom.xml b/org.eclipse.m2e.core.tests/resources/projects/enforcerSettingsWithVersion/pom.xml similarity index 100% rename from org.eclipse.m2e.jdt.tests/projects/enforcerSettingsWithVersion/pom.xml rename to org.eclipse.m2e.core.tests/resources/projects/enforcerSettingsWithVersion/pom.xml diff --git a/org.eclipse.m2e.jdt.tests/projects/enforcerSettingsWithVersionRange/pom.xml b/org.eclipse.m2e.core.tests/resources/projects/enforcerSettingsWithVersionRange/pom.xml similarity index 96% rename from org.eclipse.m2e.jdt.tests/projects/enforcerSettingsWithVersionRange/pom.xml rename to org.eclipse.m2e.core.tests/resources/projects/enforcerSettingsWithVersionRange/pom.xml index 888ab0d305..ce8f8bffaa 100644 --- a/org.eclipse.m2e.jdt.tests/projects/enforcerSettingsWithVersionRange/pom.xml +++ b/org.eclipse.m2e.core.tests/resources/projects/enforcerSettingsWithVersionRange/pom.xml @@ -35,7 +35,7 @@ - [11.0.10,16) + [11.0.6,13) diff --git a/org.eclipse.m2e.jdt.tests/projects/enforcerSettingsWithoutRequiredJavaVersion/pom.xml b/org.eclipse.m2e.core.tests/resources/projects/enforcerSettingsWithoutRequiredJavaVersion/pom.xml similarity index 100% rename from org.eclipse.m2e.jdt.tests/projects/enforcerSettingsWithoutRequiredJavaVersion/pom.xml rename to org.eclipse.m2e.core.tests/resources/projects/enforcerSettingsWithoutRequiredJavaVersion/pom.xml 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 new file mode 100644 index 0000000000..014ce12473 --- /dev/null +++ b/org.eclipse.m2e.core.tests/src/org/eclipse/m2e/internal/launch/MavenLaunchDelegateTest.java @@ -0,0 +1,169 @@ +/******************************************************************************* + * Copyright (c) 2022, 2023 Hannes Wellmann and others + * All rights reserved. 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: + * Hannes Wellmann - initial API and implementation + * Konrad Windszus - Add tests for required java runtime version implied by enforcer rule + *******************************************************************************/ + +package org.eclipse.m2e.internal.launch; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.when; + +import java.io.File; +import java.util.List; +import java.util.function.Supplier; + +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; +import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy; +import org.eclipse.debug.core.ILaunchManager; +import org.eclipse.jdt.internal.launching.StandardVMType; +import org.eclipse.jdt.launching.AbstractVMInstall; +import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants; +import org.eclipse.jdt.launching.IVMInstall; +import org.eclipse.jdt.launching.IVMInstallType; +import org.eclipse.jdt.launching.JavaRuntime; +import org.eclipse.m2e.actions.MavenLaunchConstants; +import org.eclipse.m2e.tests.common.AbstractMavenProjectTestCase; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.mockito.MockedStatic; +import org.mockito.Mockito; + +@SuppressWarnings("restriction") +public class MavenLaunchDelegateTest extends AbstractMavenProjectTestCase { + + // If there is a dedicated locations for m2e.launching tests one day, move it + // there. + + 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"); + + private static MockedStatic javaRuntimeMock; + private static IVMInstall defaultVM; + + @BeforeClass + public static void setUpJavaRuntimeMocking() { + + 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(); + + javaRuntimeMock = Mockito.mockStatic(JavaRuntime.class); + javaRuntimeMock.when(() -> JavaRuntime.getVMInstallTypes()).thenReturn(new IVMInstallType[] { standardVMType }); + javaRuntimeMock.when(() -> JavaRuntime.computeVMInstall(Mockito.any())).thenReturn(defaultVM); + } + + @AfterClass + public static void clearJavaRuntimeMocking() { + javaRuntimeMock.close(); + defaultVM = null; + } + + @Test + public void testGetBestMatchingVM_majorOnly() throws InvalidVersionSpecificationException { + assertEquals("11.0.7", MavenLaunchDelegate.getBestMatchingVM("11").getId()); + } + + @Test + public void testGetBestMatchingVM_rangeWithOnlyMajorLowerBound() throws InvalidVersionSpecificationException { + assertEquals("11.0.7", MavenLaunchDelegate.getBestMatchingVM("[11,)").getId()); + } + + @Test + public void testGetBestMatchingVM_9versionRange() throws InvalidVersionSpecificationException { + assertEquals("17.0.4", MavenLaunchDelegate.getBestMatchingVM("[11,18)").getId()); + } + + @Test + public void testGetBestMatchingVM_1XversionRange() throws InvalidVersionSpecificationException { + assertEquals("1.8.0", MavenLaunchDelegate.getBestMatchingVM("[1.8,9)").getId()); + } + + @Test + public void testRequiredJavaVersionFromEnforcerRule_Version() throws Exception { + IProject project = importProject("resources/projects/enforcerSettingsWithVersion/pom.xml"); + assertRequiredJavaBuildVersion(project, "13.0.3", "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"); + } + + @Test + public void testRequiredJavaVersionFromEnforcerRule_NoVersionRange() throws Exception { + IProject project = importProject("resources/projects/enforcerSettingsWithoutRequiredJavaVersion/pom.xml"); + assertRequiredJavaBuildVersion(project, null, DEFAULT_VM); + } + + private void assertRequiredJavaBuildVersion(IProject project, String expectedVersionRange, String expectedVMVersion) + throws Exception { + + waitForJobsToComplete(); + + File pomFile = project.getLocation().toFile(); + + assertEquals(expectedVersionRange, MavenLaunchDelegate.readEnforcedJavaVersion(pomFile, monitor)); + + List> pomDirSuppliers = List.of( + // Case 1: project loc (via variable) + () -> "${workspace_loc:/" + project.getName() + "}", + // Case 2: project outside of workspace + () -> { + try { + project.delete(false, true, monitor); + } catch (CoreException e) { + throw new IllegalStateException(e); + } + return pomFile.toString(); + }); + + for (Supplier pomDir : pomDirSuppliers) { + String pomDirStr = pomDir.get(); + ILaunchConfigurationWorkingCopy config = createMavenLaunchConfig(pomDirStr); + assertEquals(expectedVMVersion, new MavenLaunchDelegate().getVMInstall(config).getId()); + + ILaunchConfigurationWorkingCopy config2 = createMavenLaunchConfig(pomDirStr); + config2.setAttribute(IJavaLaunchConfigurationConstants.ATTR_JRE_CONTAINER_PATH, + "org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17/"); + assertEquals(DEFAULT_VM, new MavenLaunchDelegate().getVMInstall(config2).getId()); + // When a JRE_CONTAINER_PATH is set, getVMInstall hands over to + // JavaRuntime.computeVMInstall(), which is mocked in this test to always return + // the defaultVM. + } + } + + private static ILaunchConfigurationWorkingCopy createMavenLaunchConfig(String pomDir) throws CoreException { + ILaunchManager launchManager = DebugPlugin.getDefault().getLaunchManager(); + String name = launchManager.generateLaunchConfigurationName("RequiredJavaVersionFromEnforcerRuleTest"); + ILaunchConfigurationWorkingCopy config = launchManager + .getLaunchConfigurationType(MavenLaunchConstants.LAUNCH_CONFIGURATION_TYPE_ID).newInstance(null, name); + config.setAttribute(ILaunchManager.ATTR_PRIVATE, true); + config.setAttribute(MavenLaunchConstants.ATTR_POM_DIR, pomDir); + return config; + } + +} diff --git a/org.eclipse.m2e.jdt.tests/src/org/eclipse/m2e/jdt/tests/JavaConfigurationFromEnforcerTest.java b/org.eclipse.m2e.jdt.tests/src/org/eclipse/m2e/jdt/tests/JavaConfigurationFromEnforcerTest.java deleted file mode 100644 index 704103c327..0000000000 --- a/org.eclipse.m2e.jdt.tests/src/org/eclipse/m2e/jdt/tests/JavaConfigurationFromEnforcerTest.java +++ /dev/null @@ -1,70 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2022, 2022 Hannes Wellmann and others - * All rights reserved. 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: - * Hannes Wellmann - initial API and implementation - *******************************************************************************/ - -package org.eclipse.m2e.jdt.tests; - -import static org.junit.Assert.assertEquals; - -import java.util.Arrays; -import java.util.List; - -import org.eclipse.core.resources.IProject; -import org.eclipse.core.runtime.IPath; -import org.eclipse.jdt.core.IClasspathEntry; -import org.eclipse.jdt.core.IJavaProject; -import org.eclipse.jdt.core.JavaCore; -import org.eclipse.jdt.core.JavaModelException; -import org.eclipse.m2e.tests.common.AbstractMavenProjectTestCase; -import org.junit.Test; - -public class JavaConfigurationFromEnforcerTest extends AbstractMavenProjectTestCase { - private static final String JRE_CONTAINER_PREFIX = "org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/"; - - @Test - public void testEnforcer_Version() throws Exception { - IProject project = importProject("projects/enforcerSettingsWithVersion/pom.xml"); - waitForJobsToComplete(); - IJavaProject jproject = JavaCore.create(project); - assertEquals("1.8", jproject.getOption(JavaCore.COMPILER_SOURCE, false)); - assertEquals("1.8", jproject.getOption(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM, false)); - assertEquals(List.of("JavaSE-13"), getJREContainerVMType(jproject)); - } - - @Test - public void testEnforcer_VersionRange() throws Exception { - IProject project = importProject("projects/enforcerSettingsWithVersionRange/pom.xml"); - waitForJobsToComplete(); - IJavaProject jproject = JavaCore.create(project); - assertEquals("1.8", jproject.getOption(JavaCore.COMPILER_SOURCE, false)); - assertEquals("1.8", jproject.getOption(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM, false)); - assertEquals(List.of("JavaSE-11"), getJREContainerVMType(jproject)); - } - - @Test - public void testEnforcer_NoVersionRange() throws Exception { - IProject project = importProject("projects/enforcerSettingsWithoutRequiredJavaVersion/pom.xml"); - waitForJobsToComplete(); - IJavaProject jproject = JavaCore.create(project); - assertEquals("1.8", jproject.getOption(JavaCore.COMPILER_SOURCE, false)); - assertEquals("1.8", jproject.getOption(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM, false)); - assertEquals(List.of("JavaSE-1.8"), getJREContainerVMType(jproject)); - } - - - private static List getJREContainerVMType(IJavaProject jproject) throws JavaModelException { - return Arrays.stream(jproject.getRawClasspath()) - .filter(cp -> cp.getEntryKind() == IClasspathEntry.CPE_CONTAINER).map(IClasspathEntry::getPath) - .map(IPath::toString).filter(p -> p.startsWith(JRE_CONTAINER_PREFIX)) - .map(p -> p.substring(JRE_CONTAINER_PREFIX.length())).toList(); - } -} diff --git a/org.eclipse.m2e.jdt.ui/src/org/eclipse/m2e/jdt/ui/internal/messages.properties b/org.eclipse.m2e.jdt.ui/src/org/eclipse/m2e/jdt/ui/internal/messages.properties index 6839b71be5..c9b9db21db 100644 --- a/org.eclipse.m2e.jdt.ui/src/org/eclipse/m2e/jdt/ui/internal/messages.properties +++ b/org.eclipse.m2e.jdt.ui/src/org/eclipse/m2e/jdt/ui/internal/messages.properties @@ -5,5 +5,5 @@ MavenClasspathContainerPage_title=Maven Dependencies MavenPreferencePage_executionEnvironmentJreLink=See Execution Environments for the JREs used for specific execution environments. MavenPreferencePage_workspaceDefaultJreLink=See Installed JREs for the default workspace JRE. MavenPreferencePage_jreSystemLibraryVersion=JRE System Library Version -MavenPreferencePage_useExecutionEnvironment=Use execution environment based on maven-enforcer-plugin/maven-compiler-plugin configuration +MavenPreferencePage_useExecutionEnvironment=Use execution environment based on maven-compiler-plugin configuration MavenPreferencePage_useWorkspaceDefault=Use workspace default \ No newline at end of file diff --git a/org.eclipse.m2e.jdt/src/org/eclipse/m2e/jdt/internal/AbstractJavaProjectConfigurator.java b/org.eclipse.m2e.jdt/src/org/eclipse/m2e/jdt/internal/AbstractJavaProjectConfigurator.java index 229e42570a..049b7a7860 100644 --- a/org.eclipse.m2e.jdt/src/org/eclipse/m2e/jdt/internal/AbstractJavaProjectConfigurator.java +++ b/org.eclipse.m2e.jdt/src/org/eclipse/m2e/jdt/internal/AbstractJavaProjectConfigurator.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2008, 2019 Sonatype, Inc. and others. + * Copyright (c) 2008, 2023 Sonatype, Inc. and others. * All rights reserved. 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 @@ -21,7 +21,6 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -44,18 +43,12 @@ import org.eclipse.jdt.launching.environments.IExecutionEnvironment; import org.eclipse.jdt.launching.environments.IExecutionEnvironmentsManager; -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.Restriction; -import org.apache.maven.artifact.versioning.VersionRange; import org.apache.maven.model.Resource; import org.apache.maven.plugin.MojoExecution; import org.apache.maven.project.MavenProject; import org.eclipse.m2e.core.MavenPlugin; import org.eclipse.m2e.core.internal.M2EUtils; -import org.eclipse.m2e.core.internal.embedder.MavenImpl; import org.eclipse.m2e.core.project.IMavenProjectFacade; import org.eclipse.m2e.core.project.IProjectConfigurationManager; import org.eclipse.m2e.core.project.configurator.AbstractProjectConfigurator; @@ -89,12 +82,6 @@ public abstract class AbstractJavaProjectConfigurator extends AbstractProjectCon public static final String COMPILER_PLUGIN_GROUP_ID = "org.apache.maven.plugins"; - private static final String GOAL_ENFORCE = "enforce"; //$NON-NLS-1$ - - public static final String ENFORCER_PLUGIN_ARTIFACT_ID = "maven-enforcer-plugin"; //$NON-NLS-1$ - - public static final String ENFORCER_PLUGIN_GROUP_ID = "org.apache.maven.plugins"; //$NON-NLS-1$ - protected static final List RELEASES; protected static final List SOURCES; @@ -171,8 +158,7 @@ public void configure(ProjectConfigurationRequest request, IProgressMonitor moni addProjectSourceFolders(classpath, options, request, monitor); String executionEnvironmentId = getExecutionEnvironmentId(options); - String buildEnvironmentId = getMinimumJavaBuildEnvironmentId(request, monitor); - addJREClasspathContainer(classpath, buildEnvironmentId != null ? buildEnvironmentId : executionEnvironmentId); + addJREClasspathContainer(classpath, executionEnvironmentId); addMavenClasspathContainer(classpath); @@ -813,12 +799,6 @@ protected List getCompilerMojoExecutions(ProjectConfigurationRequ monitor, GOAL_COMPILE, GOAL_TESTCOMPILE); } - protected List getEnforcerMojoExecutions(ProjectConfigurationRequest request, IProgressMonitor monitor) - throws CoreException { - return request.mavenProjectFacade().getMojoExecutions(ENFORCER_PLUGIN_GROUP_ID, ENFORCER_PLUGIN_ARTIFACT_ID, - monitor, GOAL_ENFORCE); - } - private String getCompilerLevel(MavenProject mavenProject, MojoExecution execution, String parameter, String source, List levels, IProgressMonitor monitor) { int levelIdx = getLevelIndex(source, levels); @@ -861,74 +841,6 @@ private int getLevelIndex(String level, List levels) { return idx; } - private String getMinimumJavaBuildEnvironmentId(ProjectConfigurationRequest request, IProgressMonitor monitor) { - try { - List mojoExecutions = getEnforcerMojoExecutions(request, monitor); - for(MojoExecution mojoExecution : mojoExecutions) { - String version = getMinimumJavaBuildEnvironmentId(request.mavenProject(), mojoExecution, monitor); - if(version != null) { - return version; - } - } - } catch(CoreException | InvalidVersionSpecificationException ex) { - log.error("Failed to determine minimum build Java version, assuming default", ex); - } - return null; - } - - private String getMinimumJavaBuildEnvironmentId(MavenProject mavenProject, MojoExecution mojoExecution, - IProgressMonitor monitor) throws InvalidVersionSpecificationException, CoreException { - // https://maven.apache.org/enforcer/enforcer-rules/requireJavaVersion.html - String version = ((MavenImpl) maven).getMojoParameterValue(mavenProject, mojoExecution, - List.of("rules", "requireJavaVersion", "version"), String.class, monitor); - if(version == null) { - return null; - } - return getMinimumJavaBuildEnvironmentId(version); - } - - private String getMinimumJavaBuildEnvironmentId(String versionSpec) throws InvalidVersionSpecificationException { - VersionRange vr = VersionRange.createFromVersionSpec(versionSpec); - ArtifactVersion recommendedVersion = vr.getRecommendedVersion(); - List versionRestrictions = List.of(); - if(recommendedVersion == null) { - versionRestrictions = getVersionRangeRestrictionsIgnoringMicroAndQualifier(vr); - } else { - // only consider major and minor version here, micro and qualifier not relevant inside IDE (probably) - recommendedVersion = getMajorMinorOnlyVersion(recommendedVersion); - } - // find lowest matching environment id - for(Entry entry : ENVIRONMENTS.entrySet()) { - ArtifactVersion environmentVersion = new DefaultArtifactVersion(entry.getKey()); - boolean foundMatchingVersion; - if(recommendedVersion == null) { - foundMatchingVersion = versionRestrictions.stream().anyMatch(r -> r.containsVersion(environmentVersion)); - } else { - // only singular versions ever have a recommendedVersion - int compareTo = recommendedVersion.compareTo(environmentVersion); - foundMatchingVersion = compareTo <= 0; - } - if(foundMatchingVersion) { - return entry.getValue(); - } - } - return null; - } - - private static List getVersionRangeRestrictionsIgnoringMicroAndQualifier(VersionRange versionRange) { - return versionRange.getRestrictions().stream().map(restriction -> { - ArtifactVersion lowerBound = restriction.getLowerBound(); - ArtifactVersion upperBound = restriction.getUpperBound(); - return new Restriction(// - lowerBound != null ? getMajorMinorOnlyVersion(lowerBound) : null, restriction.isLowerBoundInclusive(), - upperBound != null ? getMajorMinorOnlyVersion(upperBound) : null, restriction.isUpperBoundInclusive()); - }).toList(); - } - - private static ArtifactVersion getMajorMinorOnlyVersion(ArtifactVersion lower) { - return new DefaultArtifactVersion(lower.getMajorVersion() + "." + lower.getMinorVersion()); - } - private double asDouble(String level) { if(level == null || level.isEmpty()) { return -1; diff --git a/org.eclipse.m2e.launching/src/org/eclipse/m2e/actions/ExecutePomAction.java b/org.eclipse.m2e.launching/src/org/eclipse/m2e/actions/ExecutePomAction.java index 6a3d098f12..b0efee9666 100644 --- a/org.eclipse.m2e.launching/src/org/eclipse/m2e/actions/ExecutePomAction.java +++ b/org.eclipse.m2e.launching/src/org/eclipse/m2e/actions/ExecutePomAction.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2008, 2019 Sonatype, Inc. and others. + * Copyright (c) 2008, 2023 Sonatype, Inc. and others. * All rights reserved. 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 @@ -42,11 +42,6 @@ import org.eclipse.debug.ui.ILaunchGroup; import org.eclipse.debug.ui.ILaunchShortcut; import org.eclipse.debug.ui.RefreshTab; -import org.eclipse.jdt.core.IClasspathEntry; -import org.eclipse.jdt.core.IJavaProject; -import org.eclipse.jdt.core.JavaCore; -import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants; -import org.eclipse.jdt.launching.JavaRuntime; import org.eclipse.jface.viewers.ILabelProvider; import org.eclipse.jface.viewers.ILabelProviderListener; import org.eclipse.jface.viewers.ISelection; @@ -216,11 +211,6 @@ private ILaunchConfiguration createLaunchConfiguration(IContainer basedir, Strin setProjectConfiguration(workingCopy, basedir); - IPath path = getJREContainerPath(basedir); - if(path != null) { - workingCopy.setAttribute(IJavaLaunchConfigurationConstants.ATTR_JRE_CONTAINER_PATH, path.toPortableString()); - } - // TODO when launching Maven with debugger consider to add the following property // -Dmaven.surefire.debug="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -Xnoagent -Djava.compiler=NONE" @@ -245,21 +235,6 @@ private void setProjectConfiguration(ILaunchConfigurationWorkingCopy workingCopy } } - // TODO ideally it should use MavenProject, but it is faster to scan IJavaProjects - private IPath getJREContainerPath(IContainer basedir) throws CoreException { - IProject project = basedir.getProject(); - if(project != null && project.hasNature(JavaCore.NATURE_ID)) { - IJavaProject javaProject = JavaCore.create(project); - IClasspathEntry[] entries = javaProject.getRawClasspath(); - for(IClasspathEntry entry : entries) { - if(JavaRuntime.JRE_CONTAINER.equals(entry.getPath().segment(0))) { - return entry.getPath(); - } - } - } - return null; - } - private ILaunchConfiguration getLaunchConfiguration(IContainer basedir, String mode) { if(goalName != null) { return createLaunchConfiguration(basedir, goalName); 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 eb22e44d18..18552f5b2a 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 @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2008, 2019 Sonatype, Inc. + * Copyright (c) 2008, 2023 Sonatype, Inc. * All rights reserved. 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 @@ -20,35 +20,66 @@ 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.Set; +import java.util.SortedMap; +import java.util.TreeMap; +import java.util.function.Supplier; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.eclipse.core.resources.IContainer; +import org.eclipse.core.resources.IFile; +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.IProgressMonitor; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.preferences.IPreferencesService; import org.eclipse.debug.core.ILaunch; import org.eclipse.debug.core.ILaunchConfiguration; +import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy; +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.execution.MavenExecutionResult; +import org.apache.maven.plugin.MojoExecution; +import org.apache.maven.project.MavenProject; import org.eclipse.m2e.actions.MavenLaunchConstants; import org.eclipse.m2e.core.MavenPlugin; import org.eclipse.m2e.core.embedder.IMavenConfiguration; +import org.eclipse.m2e.core.embedder.IMavenExecutionContext; +import org.eclipse.m2e.core.internal.IMavenConstants; +import org.eclipse.m2e.core.internal.IMavenToolbox; import org.eclipse.m2e.core.internal.launch.AbstractMavenRuntime; +import org.eclipse.m2e.core.internal.project.registry.MavenProjectFacade; +import org.eclipse.m2e.core.project.IMavenProjectFacade; +import org.eclipse.m2e.core.project.IMavenProjectRegistry; import org.eclipse.m2e.internal.launch.MavenRuntimeLaunchSupport.VMArguments; public class MavenLaunchDelegate extends JavaLaunchDelegate implements MavenLaunchConstants { - static final Logger log = LoggerFactory.getLogger(MavenLaunchDelegate.class); + 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$ @@ -163,6 +194,202 @@ public String getProgramArguments(ILaunchConfiguration configuration) throws Cor return programArguments; } + public static File getPomDirectory(ILaunchConfiguration configuration) { + if(configuration == null) { + return null; + } + // set associated project name (if there is some) + String pomDir; + try { + pomDir = configuration.getAttribute(MavenLaunchConstants.ATTR_POM_DIR, ""); + } catch(CoreException ex) { + log.warn("Failed to retrieve attribute '{}' from launch configuration {}", MavenLaunchConstants.ATTR_POM_DIR, + configuration.getName()); + return null; + } + try { + return new File(LaunchingUtils.substituteVar(pomDir)); + } catch(CoreException e) { + log.debug("Cannot substitute vars in {}", pomDir, e); + return null; + } + } + + public static Optional getContainer(File file) { + // try to retrieve associated Eclipse project + return Optional.ofNullable(file).map(f -> Path.fromOSString(f.toString())) + .map(ResourcesPlugin.getWorkspace().getRoot()::getContainerForLocation); + } + + @Override + public IVMInstall getVMInstall(ILaunchConfiguration configuration) throws CoreException { + // 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 project = getContainer(pomDirectory).map(IContainer::getProject); + if(project.isPresent()) { + // Set the project name so that super.getVMInstall() called below, can find the JDT-Compiler JDK + ILaunchConfigurationWorkingCopy workingCopy = configuration.getWorkingCopy(); + workingCopy.setAttribute(IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME, project.get().getName()); + configuration = workingCopy; + } + return super.getVMInstall(configuration); + } + + public static String readEnforcedJavaVersion(File pomDirectory, IProgressMonitor monitor) { + try { + Optional container = getContainer(pomDirectory); + if(container.isPresent()) { + IMavenProjectRegistry projectManager = MavenPlugin.getMavenProjectRegistry(); + IFile pomFile = container.get().getFile(Path.fromOSString(IMavenConstants.POM_FILE_NAME)); + IMavenProjectFacade mavenProject = projectManager.create(pomFile, true, new NullProgressMonitor()); + if(mavenProject != null) { + return readEnforcedVersion(mavenProject.getMavenProject(null), mavenProject::createExecutionContext, monitor); + } + } else if(pomDirectory != null) { + return loadEnforcedJavaVersionFromMavenProject(pomDirectory); + } + } catch(CoreException ex) { + logEnforcedJavaVersionCalculationError(ex); + } + return null; + } + + private static String loadEnforcedJavaVersionFromMavenProject(File pomFile) throws CoreException { + if(!pomFile.isFile()) { + pomFile = new File(pomFile, IMavenConstants.POM_FILE_NAME); + if(!pomFile.isFile()) { + //TODO: try to load pom-less projects?! + return null; + } + } + List poms = List.of(pomFile); + return IMavenExecutionContext.of(pomFile).execute((ctx, monitor) -> { + var projects = IMavenToolbox.of(ctx).readMavenProjects(poms, ctx.newProjectBuildingRequest()).values(); + if(projects.isEmpty()) { + return null; + } + MavenExecutionResult result = projects.iterator().next(); + if(result.hasExceptions()) { + result.getExceptions().forEach(MavenLaunchDelegate::logEnforcedJavaVersionCalculationError); + return null; + } + return readEnforcedVersion(result.getProject(), () -> ctx, monitor); + }, 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(MavenProject project, Supplier context, + IProgressMonitor monitor) throws CoreException { + @SuppressWarnings("restriction") + List mojoExecutions = MavenProjectFacade.getMojoExecutions(project, ENFORCER_PLUGIN_GROUP_ID, + ENFORCER_PLUGIN_ARTIFACT_ID, Set.of(GOAL_ENFORCE), context, + MavenLaunchDelegate::logEnforcedJavaVersionCalculationError, monitor); + for(MojoExecution mojoExecution : mojoExecutions) { + String version = getRequiredJavaVersionFromEnforcerRule(project, 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); + + // for ranges with only lower bound or just a recommended version pick newest version having equal major 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) { + return matchingJREs.stream() + .filter(jre -> getArtifactVersion(jre).getMajorVersion() == mainVersion.getMajorVersion()).findFirst() + .orElse(null); + } + return !matchingJREs.isEmpty() ? matchingJREs.get(0) : null; + } 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)) { + if(vm instanceof IVMInstall2 vm2) { + installedJREsByVersion.put(new DefaultArtifactVersion(vm2.getJavaVersion()), 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 = getArtifactVersion(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 getArtifactVersion(IVMInstall jre) { + return jre instanceof IVMInstall2 jre2 ? new DefaultArtifactVersion(jre2.getJavaVersion()) : DEFAULT_JAVA_VERSION; + } + @Override public String getVMArguments(ILaunchConfiguration configuration) throws CoreException { VMArguments arguments = launchSupport.getVMArguments(); diff --git a/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/Messages.java b/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/Messages.java index f9216c61f7..027dbdc165 100644 --- a/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/Messages.java +++ b/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/Messages.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2008-2010 Sonatype, Inc. + * Copyright (c) 2008-2023 Sonatype, Inc. * All rights reserved. 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 @@ -81,6 +81,10 @@ public class Messages extends NLS { public static String MavenLaunchMainTab_property_dialog_title; + public static String MavenJRETab_lblDefault; + + public static String MavenJRETab_lblDefaultDetailsRequiredJavaVersion; + public static String MavenLaunchExtensionsTab_name; public static String MavenLaunchExtensionsTab_lblExtensions; diff --git a/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/messages.properties b/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/messages.properties index 2370e2231c..e87792f0ae 100644 --- a/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/messages.properties +++ b/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/messages.properties @@ -33,6 +33,8 @@ MavenLaunchMainTab_lblEnableColorOutput_Never=Never MavenLaunchMainTab_lblUserSettings_text=User settings\: MavenLaunchMainTab_property_dialog_edit_title=Edit Parameter MavenLaunchMainTab_property_dialog_title=Add Parameter +MavenJRETab_lblDefault=Default Maven Execution JRE: {0} +MavenJRETab_lblDefaultDetailsRequiredJavaVersion={0} (auto-selected from required Java version range {1}) MavenLaunchUtils_error_no_maven_install=Can't find Maven installation {0} MavenLaynchDelegate_unsupported_source_locator=Unknown or unsupported source locator {0} launchBrowseFs=File Syste&m... 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 dfd6ed833e..dab0346951 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 @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2008-2010 Sonatype, Inc. + * Copyright (c) 2008-2023 Sonatype, Inc. * All rights reserved. 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 @@ -13,15 +13,29 @@ package org.eclipse.m2e.ui.internal.launch; +import java.io.File; + +import org.eclipse.core.resources.IContainer; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.runtime.IStatus; import org.eclipse.debug.core.ILaunchConfiguration; import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy; import org.eclipse.debug.ui.ILaunchConfigurationDialog; +import org.eclipse.debug.ui.ILaunchConfigurationTab; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.debug.ui.launchConfigurations.JavaJRETab; +import org.eclipse.jdt.internal.debug.ui.jres.JREDescriptor; import org.eclipse.jdt.internal.debug.ui.launcher.VMArgumentsBlock; +import org.eclipse.jdt.launching.IVMInstall; +import org.eclipse.osgi.util.NLS; import org.eclipse.swt.SWT; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.widgets.Composite; +import org.eclipse.m2e.internal.launch.MavenLaunchDelegate; +import org.eclipse.m2e.internal.launch.Messages; + @SuppressWarnings("restriction") public class MavenJRETab extends JavaJRETab { @@ -40,6 +54,67 @@ public void createControl(Composite parent) { ((GridData) vmArgumentsBlock.getControl().getLayoutData()).horizontalSpan = 2; } + @Override + protected IJavaProject getJavaProject() { + File pomDir = MavenLaunchDelegate.getPomDirectory(getLaunchConfiguration()); + return MavenLaunchDelegate.getContainer(pomDir) // + .map(IContainer::getProject).filter(IProject::exists) // + .map(JavaCore::create).orElse(null); + } + + /** + * Retrieves information about Maven JRE set in Maven project properties. + * + * @return the descriptor for the default Maven JRE + */ + @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 { + // TODO: add logic for getting the underlying project then fall back to default + details = super.getDefaultJREDescriptor().getDescription(); + } + return new JREDescriptor() { + @Override + public String getDescription() { + return NLS.bind(Messages.MavenJRETab_lblDefault, details); + } + }; + } + + /** + * Need to overwrite from parent, to prevent calling JavaJRETab.checkCompliance(). + */ + @Override + public boolean isValid(ILaunchConfiguration config) { + setErrorMessage(null); + setMessage(null); + + IStatus status = fJREBlock.getStatus(); + if(!status.isOK()) { + setErrorMessage(status.getMessage()); + return false; + } + // prevent calling JavaJRETab.checkCompliance(), as that uses the wrong JDK for the checks when the default is checked + // also Maven launch type is not detected as external program + //TODO: isExternalToolConfiguration() should be protected and be overrideable + /** + * if(!isExternalToolConfiguration(fLaunchConfiguration)) { status = checkCompliance(); if (!status.isOK()) { + * setErrorMessage(status.getMessage()); return false; } } + */ + + ILaunchConfigurationTab dynamicTab = getDynamicTab(); + if(dynamicTab != null) { + return dynamicTab.isValid(config); + } + return true; + } + @Override public void performApply(ILaunchConfigurationWorkingCopy configuration) { super.performApply(configuration);