From 27cd558bbbac08499cc867d7601b150ab32fd259 Mon Sep 17 00:00:00 2001 From: Konrad Windszus Date: Mon, 12 Dec 2022 17:48:57 +0100 Subject: [PATCH] Configure Maven JRE separately The value is automatically derived from maven-enforcer rule requireJava This closes #1134 This closes #1099 --- .../META-INF/MANIFEST.MF | 4 +- .../enforcerSettingsWithVersion/pom.xml | 0 .../enforcerSettingsWithVersionRange/pom.xml | 0 .../pom.xml | 0 .../m2e/core/TestJREListingService.java | 110 ++++++++++ .../project/MavenJreFromEnforcerTest.java | 72 +++++++ org.eclipse.m2e.core.ui/META-INF/MANIFEST.MF | 2 + .../m2e/core/ui/internal/Messages.java | 12 ++ .../m2e/core/ui/internal/messages.properties | 6 + .../MavenProjectPreferencePage.java | 202 +++++++++++++++++- org.eclipse.m2e.core/META-INF/MANIFEST.MF | 3 + .../internal/IInstalledJREListingService.java | 38 ++++ .../internal/InstalledJREListingService.java | 79 +++++++ .../project/ProjectConfigurationManager.java | 159 ++++++++++++++ .../project/ResolverConfigurationIO.java | 11 + .../project/registry/MavenProjectFacade.java | 16 +- .../core/project/IProjectConfiguration.java | 7 + .../core/project/ResolverConfiguration.java | 27 +++ .../JavaConfigurationFromEnforcerTest.java | 70 ------ .../AbstractJavaProjectConfigurator.java | 90 +------- .../eclipse/m2e/actions/ExecutePomAction.java | 7 +- .../internal/launch/MavenLaunchDelegate.java | 41 ++++ .../m2e/ui/internal/launch/MavenJRETab.java | 86 ++++++++ .../internal/launch/MavenLaunchMainTab.java | 17 ++ 24 files changed, 888 insertions(+), 171 deletions(-) rename {org.eclipse.m2e.jdt.tests => org.eclipse.m2e.core.tests/resources}/projects/enforcerSettingsWithVersion/pom.xml (100%) rename {org.eclipse.m2e.jdt.tests => org.eclipse.m2e.core.tests/resources}/projects/enforcerSettingsWithVersionRange/pom.xml (100%) rename {org.eclipse.m2e.jdt.tests => org.eclipse.m2e.core.tests/resources}/projects/enforcerSettingsWithoutRequiredJavaVersion/pom.xml (100%) create mode 100644 org.eclipse.m2e.core.tests/src/org/eclipse/m2e/core/TestJREListingService.java create mode 100644 org.eclipse.m2e.core.tests/src/org/eclipse/m2e/core/internal/project/MavenJreFromEnforcerTest.java create mode 100644 org.eclipse.m2e.core/src/org/eclipse/m2e/core/internal/IInstalledJREListingService.java create mode 100644 org.eclipse.m2e.core/src/org/eclipse/m2e/core/internal/InstalledJREListingService.java delete mode 100644 org.eclipse.m2e.jdt.tests/src/org/eclipse/m2e/jdt/tests/JavaConfigurationFromEnforcerTest.java diff --git a/org.eclipse.m2e.core.tests/META-INF/MANIFEST.MF b/org.eclipse.m2e.core.tests/META-INF/MANIFEST.MF index d256b2bd5c..7af2ca3118 100644 --- a/org.eclipse.m2e.core.tests/META-INF/MANIFEST.MF +++ b/org.eclipse.m2e.core.tests/META-INF/MANIFEST.MF @@ -11,6 +11,8 @@ Require-Bundle: org.eclipse.m2e.tests.common, org.eclipse.core.resources, org.eclipse.core.runtime, org.eclipse.m2e.maven.runtime -Import-Package: javax.annotation;version="1.2.0" +Import-Package: javax.annotation;version="1.2.0", + org.eclipse.jdt.internal.launching, + org.eclipse.jdt.launching.environments 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 100% rename from org.eclipse.m2e.jdt.tests/projects/enforcerSettingsWithVersionRange/pom.xml rename to org.eclipse.m2e.core.tests/resources/projects/enforcerSettingsWithVersionRange/pom.xml 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/core/TestJREListingService.java b/org.eclipse.m2e.core.tests/src/org/eclipse/m2e/core/TestJREListingService.java new file mode 100644 index 0000000000..5a70254ca9 --- /dev/null +++ b/org.eclipse.m2e.core.tests/src/org/eclipse/m2e/core/TestJREListingService.java @@ -0,0 +1,110 @@ +/******************************************************************************** + * Copyright (c) 2022, 2022 Konrad Windszus 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: + * Konrad Windszus - initial API and implementation + ********************************************************************************/ + +package org.eclipse.m2e.core; + +import java.util.Collection; +import java.util.Map; +import java.util.Optional; + +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.jdt.internal.launching.JREContainerInitializer; +import org.eclipse.jdt.launching.AbstractVMInstall; +import org.eclipse.jdt.launching.IVMInstall; +import org.eclipse.m2e.core.internal.IInstalledJREListingService; +import org.osgi.framework.Constants; +import org.osgi.service.component.annotations.Component; + +/** + * Replaces the default {@link IInstalledJREListingService} from m2e.core with a hard-coded list of JREs per execution environment id. + */ +@SuppressWarnings("restriction") +@Component(property = Constants.SERVICE_RANKING + ":Integer=1") +public class TestJREListingService implements IInstalledJREListingService { + + private static final Map INSTALLED_JRES_PER_ENVIRONMENT_ID = Map.of( + "JavaSE-1.8", new TestJREListingService.JRE("1.8"), + "JavaSE-11", new TestJREListingService.JRE("11.0.4"), + "JavaSE-13", new TestJREListingService.JRE("13.0.1")); + + private static final class JRE extends AbstractVMInstall implements IVMInstall { + + public JRE(String id) { + super(null, id); + setName(id); + } + + @Override + public String getJavaVersion() { + return getId(); + } + } + + @Override + public Collection getAllInstalledJREs() { + return INSTALLED_JRES_PER_ENVIRONMENT_ID.values(); + } + + @Override + public Optional getProjectBuildJRE(IProject project) { + if (project == null || !project.exists()) { + throw new IllegalStateException("Non existing project"); + } + final IJavaProject javaProject; + javaProject = JavaCore.create(project); + if (!javaProject.exists()) { + throw new IllegalStateException("Java project does not exist for " + project.getName()); + } + IClasspathEntry[] classpath; + try { + classpath = javaProject.getRawClasspath(); + } catch (JavaModelException e) { + throw new IllegalStateException("Could not get raw classpath from java project" + project.getName()); + } + IClasspathEntry entry = null; + for (int i = 0; i < classpath.length; i++) { + entry = classpath[i]; + switch (entry.getEntryKind()) { + case IClasspathEntry.CPE_VARIABLE: + throw new IllegalStateException("Classpath entries of type CPE_VARIABLE not supported"); + case IClasspathEntry.CPE_CONTAINER: + return Optional.of(resolveJRE(entry.getPath())); + } + } + throw new IllegalStateException("No proper Java build classpath set on project " + project); + } + + private static IVMInstall resolveJRE(IPath containerPath) { + if (containerPath.segmentCount() > 1) { + @SuppressWarnings("restriction") + String id = JREContainerInitializer.getExecutionEnvironmentId(containerPath); + if (id != null) { + return resolveJRE(id); + } else { + throw new IllegalStateException( + "Could not extract execution environment from classpath entry " + containerPath); + } + } else { + throw new IllegalStateException("Unsupported container classpath " + containerPath); + } + } + + private static IVMInstall resolveJRE(String environmentId) { + return INSTALLED_JRES_PER_ENVIRONMENT_ID.get(environmentId); + } +} diff --git a/org.eclipse.m2e.core.tests/src/org/eclipse/m2e/core/internal/project/MavenJreFromEnforcerTest.java b/org.eclipse.m2e.core.tests/src/org/eclipse/m2e/core/internal/project/MavenJreFromEnforcerTest.java new file mode 100644 index 0000000000..47e2967e43 --- /dev/null +++ b/org.eclipse.m2e.core.tests/src/org/eclipse/m2e/core/internal/project/MavenJreFromEnforcerTest.java @@ -0,0 +1,72 @@ +/******************************************************************************* + * 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.core.internal.project; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import org.eclipse.core.resources.IProject; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.JavaCore; +import org.eclipse.jdt.launching.IVMInstall; +import org.eclipse.jdt.launching.IVMInstall2; +import org.eclipse.jdt.launching.JavaRuntime; +import org.eclipse.m2e.tests.common.AbstractMavenProjectTestCase; +import org.junit.Test; + +public class MavenJreFromEnforcerTest extends AbstractMavenProjectTestCase { + + @Test + public void testEnforcer_Version() throws Exception { + IProject project = importProject("projects/enforcerSettingsWithVersion/pom.xml"); + waitForJobsToComplete(); + assertMavenExecutionJRE(project, "13.0.1"); + assertBuildJRE(project, "1.8"); + } + + @Test + public void testEnforcer_VersionRange() throws Exception { + IProject project = importProject("projects/enforcerSettingsWithVersionRange/pom.xml"); + waitForJobsToComplete(); + assertMavenExecutionJRE(project, "11.0.4"); + assertBuildJRE(project, "1.8"); + } + + @Test + public void testEnforcer_NoVersionRange() throws Exception { + IProject project = importProject("projects/enforcerSettingsWithoutRequiredJavaVersion/pom.xml"); + waitForJobsToComplete(); + assertMavenExecutionJRE(project, "1.8"); + assertBuildJRE(project, "1.8"); + } + + private void assertBuildJRE(IProject project, String expectedJavaVersion) throws CoreException { + IJavaProject jproject = JavaCore.create(project); + if (jproject == null || jproject.exists()) { + fail("Imported project is no Java project"); + } + IVMInstall buildJRE = JavaRuntime.getVMInstall(jproject); + if (buildJRE instanceof IVMInstall2 buildJRE2) { + assertEquals("Found wrong Build JRE", expectedJavaVersion, buildJRE2.getJavaVersion()); + } + } + + private void assertMavenExecutionJRE(IProject project, String expectedJavaVersion) { + IVMInstall mavenExecutionJRE = ResolverConfigurationIO.readResolverConfiguration(project).getMavenJre(); + if (mavenExecutionJRE instanceof IVMInstall2 mavenExecutionJRE2) { + assertEquals("Found wrong Maven Execution JRE", expectedJavaVersion, mavenExecutionJRE2.getJavaVersion()); + } + } +} diff --git a/org.eclipse.m2e.core.ui/META-INF/MANIFEST.MF b/org.eclipse.m2e.core.ui/META-INF/MANIFEST.MF index 5d0c962da7..5e63f39e13 100644 --- a/org.eclipse.m2e.core.ui/META-INF/MANIFEST.MF +++ b/org.eclipse.m2e.core.ui/META-INF/MANIFEST.MF @@ -43,6 +43,8 @@ Require-Bundle: org.eclipse.m2e.core;bundle-version="[2.0.0,3.0.0)", org.eclipse.core.filebuffers, org.eclipse.ui Import-Package: org.eclipse.compare.rangedifferencer, + org.eclipse.jdt.core, + org.eclipse.jdt.launching, org.eclipse.ltk.core.refactoring, org.slf4j;version="[1.7.0,3.0.0)" Service-Component: OSGI-INF/component.xml, diff --git a/org.eclipse.m2e.core.ui/src/org/eclipse/m2e/core/ui/internal/Messages.java b/org.eclipse.m2e.core.ui/src/org/eclipse/m2e/core/ui/internal/Messages.java index 2b236bc00e..906854b9fc 100644 --- a/org.eclipse.m2e.core.ui/src/org/eclipse/m2e/core/ui/internal/Messages.java +++ b/org.eclipse.m2e.core.ui/src/org/eclipse/m2e/core/ui/internal/Messages.java @@ -423,6 +423,18 @@ public class Messages extends NLS { public static String MavenProjectPreferencePage_title; + public static String MavenProjectPreferencePage_defaultWorkspaceJRE; + + public static String MavenProjectPreferencePage_defaultProjectJRE; + + public static String MavenProjectPreferencePage_mavenJRE; + + public static String MavenProjectPreferencePage_alternateJRE; + + public static String MavenProjectPreferencePage_btnInstalledJREs; + + public static String MavenProjectPreferencePage_jreNotSet; + public static String MavenProjectWizardArchetypePage_add_title; public static String MavenProjectWizardArchetypePage_all; diff --git a/org.eclipse.m2e.core.ui/src/org/eclipse/m2e/core/ui/internal/messages.properties b/org.eclipse.m2e.core.ui/src/org/eclipse/m2e/core/ui/internal/messages.properties index 876a946a77..d10899de25 100644 --- a/org.eclipse.m2e.core.ui/src/org/eclipse/m2e/core/ui/internal/messages.properties +++ b/org.eclipse.m2e.core.ui/src/org/eclipse/m2e/core/ui/internal/messages.properties @@ -242,6 +242,12 @@ MavenProjectPreferencePage_dialog_title=Maven Settings MavenProjectPreferencePage_job=Updating {0} Sources MavenProjectPreferencePage_lblProfiles=Active Maven &Profiles (comma separated)\: MavenProjectPreferencePage_title=Maven +MavenProjectPreferencePage_defaultWorkspaceJRE=Default Workspace JRE ({0}) +MavenProjectPreferencePage_defaultProjectJRE=Default Project JRE ({0}) +MavenProjectPreferencePage_mavenJRE=Maven JRE: +MavenProjectPreferencePage_alternateJRE=Alternate JRE: +MavenProjectPreferencePage_btnInstalledJREs=Installed JREs... +MavenProjectPreferencePage_jreNotSet=Not set MavenProjectWizardArchetypePage_add_title=Add Archetype MavenProjectWizardArchetypePage_all=All Catalogs MavenProjectWizardArchetypePage_btnAdd=&Add Archetype... diff --git a/org.eclipse.m2e.core.ui/src/org/eclipse/m2e/core/ui/internal/preferences/MavenProjectPreferencePage.java b/org.eclipse.m2e.core.ui/src/org/eclipse/m2e/core/ui/internal/preferences/MavenProjectPreferencePage.java index ff4fbbfd8b..de64ab4a10 100644 --- a/org.eclipse.m2e.core.ui/src/org/eclipse/m2e/core/ui/internal/preferences/MavenProjectPreferencePage.java +++ b/org.eclipse.m2e.core.ui/src/org/eclipse/m2e/core/ui/internal/preferences/MavenProjectPreferencePage.java @@ -13,6 +13,12 @@ package org.eclipse.m2e.core.ui.internal.preferences; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Objects; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -22,16 +28,31 @@ import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.JavaCore; +import org.eclipse.jdt.launching.IVMInstall; +import org.eclipse.jdt.launching.IVMInstallType; +import org.eclipse.jdt.launching.JavaRuntime; +import org.eclipse.jdt.launching.VMStandin; import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.window.Window; import org.eclipse.osgi.util.NLS; import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Combo; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Group; import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Text; +import org.eclipse.ui.dialogs.PreferencesUtil; import org.eclipse.ui.dialogs.PropertyPage; import org.eclipse.m2e.core.MavenPlugin; @@ -54,10 +75,26 @@ public class MavenProjectPreferencePage extends PropertyPage { // private Button includeModulesButton; + //https://git.eclipse.org/c/jdt/eclipse.jdt.debug.git/tree/org.eclipse.jdt.debug.ui/plugin.xml#n2467 + private static final String INSTALLED_JRE_PAGE = "org.eclipse.jdt.debug.ui.preferences.VMPreferencePage"; //$NON-NLS-1$ + private Text selectedProfilesText; + private Button defaultJRERadioButton; + + private Button alternateJREsRadioButton; + + private Combo alternateJREsCombo; + + /** + * VMs being displayed + */ + private List vmInstalls; + public MavenProjectPreferencePage() { setTitle(Messages.MavenProjectPreferencePage_title); + vmInstalls = getInstalledJREs(); + } @Override @@ -98,21 +135,177 @@ protected Control createContents(Composite parent) { // + "source folders from nested modules are added to the current " // + "project build path (use \"Update Sources\" action)"); - init(getResolverConfiguration()); + createJREControls(composite); + init(getResolverConfiguration()); return composite; } + private IJavaProject getCurrentJavaProject() { + IProject project = getElement().getAdapter(IProject.class); + if(project != null) { + IJavaProject javaProject = JavaCore.create(project); + if(javaProject.exists()) { + return javaProject; + } + } + return null; + } + + private String getDefaultJreName() { + IJavaProject javaProject = getCurrentJavaProject(); + String jreName; + if(javaProject == null) { + IVMInstall vmInstall = JavaRuntime.getDefaultVMInstall(); + if(vmInstall == null) { + jreName = Messages.MavenProjectPreferencePage_jreNotSet; + } else { + jreName = vmInstall.getName(); + } + return NLS.bind(Messages.MavenProjectPreferencePage_defaultWorkspaceJRE, jreName); + } + try { + IVMInstall vmInstall = JavaRuntime.getVMInstall(javaProject); + jreName = vmInstall.getName(); + } catch(CoreException ex) { + jreName = "unknown"; + } + return NLS.bind(Messages.MavenProjectPreferencePage_defaultProjectJRE, jreName); + } + + /** + * Creates JRE selection controls. Inspired by // + * (https://github.com/eclipse-jdt/eclipse.jdt.debug/blob/0c8c6d9fa30cda3c83741613f6ea4c541c037973/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/jres/JREsComboBlock.java) + * + * @param container the container + */ + private void createJREControls(Composite container) { + Group group = new Group(container, SWT.LEFT); + group.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 2, 1)); + group.setLayout(new GridLayout(1, false)); + group.setText(Messages.MavenProjectPreferencePage_mavenJRE); + + Composite composite = new Composite(group, SWT.FILL); + composite.setLayout(new GridLayout(3, false)); + composite.setLayoutData(new GridData(GridData.FILL_BOTH)); + + createDefaultJREControls(composite); + createAlternateJREControls(composite); + } + + private void createDefaultJREControls(Composite comp) { + defaultJRERadioButton = new Button(comp, SWT.RADIO); + defaultJRERadioButton.setText(getDefaultJreName()); + defaultJRERadioButton.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, false, false, 3, 1)); + defaultJRERadioButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + if(defaultJRERadioButton.getSelection()) { + alternateJREsCombo.setEnabled(false); + } + } + }); + } + + public void createAlternateJREControls(Composite comp) { + alternateJREsRadioButton = new Button(comp, SWT.RADIO); + alternateJREsRadioButton.setText(Messages.MavenProjectPreferencePage_alternateJRE); + alternateJREsRadioButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + if(alternateJREsRadioButton.getSelection()) { + alternateJREsCombo.setEnabled(true); + } + } + }); + + alternateJREsCombo = new Combo(comp, SWT.DROP_DOWN | SWT.READ_ONLY); + alternateJREsCombo.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + alternateJREsCombo.setItems(vmInstalls.stream().map(IVMInstall::getName).toArray(String[]::new)); + alternateJREsCombo.setVisibleItemCount(Math.min(vmInstalls.size(), 20)); + alternateJREsCombo.setEnabled(false); + + Button installedJREsButton = new Button(comp, SWT.PUSH); + installedJREsButton.setText(Messages.MavenProjectPreferencePage_btnInstalledJREs); + installedJREsButton.addListener(SWT.Selection, new Listener() { + @Override + public void handleEvent(Event event) { + if(showPreferencePage(INSTALLED_JRE_PAGE, comp.getShell()) == Window.OK) { + vmInstalls = getInstalledJREs(); + alternateJREsCombo.setItems(vmInstalls.stream().map(IVMInstall::getName).toArray(String[]::new)); + } + } + }); + } + + /** + * Displays the given preference page + * + * @param pageId the fully qualified id of the preference page, e.g. + * org.eclipse.jdt.debug.ui.preferences.VMPreferencePage + * @since 3.3 + */ + public static int showPreferencePage(String pageId, Shell shell) { + return PreferencesUtil.createPreferenceDialogOn(shell, pageId, new String[] {pageId}, null).open(); + } + + + /** + * @return the selected alternate JRE or null if none. + */ + private IVMInstall getJRE() { + if(alternateJREsRadioButton.getSelection()) { + int index = alternateJREsCombo.getSelectionIndex(); + if(index >= 0) { + return vmInstalls.get(index); + } + } + return null; + } + + public static List getInstalledJREs() { + // TODO: use InstalledJREListingService + List standins = new ArrayList<>(); + for (IVMInstallType type : JavaRuntime.getVMInstallTypes()) { + for (IVMInstall install : type.getVMInstalls()) { + standins.add(new VMStandin(install)); + } + } + // sort by name + Collections.sort(standins, new Comparator() { + @Override + public int compare(Object o1, Object o2) { + IVMInstall left = (IVMInstall) o1; + IVMInstall right = (IVMInstall) o2; + return left.getName().compareToIgnoreCase(right.getName()); + } + + @Override + public boolean equals(Object obj) { + return obj == this; + } + }); + return standins; + } + @Override protected void performDefaults() { init(new ResolverConfiguration()); } private void init(IProjectConfiguration configuration) { - resolveWorspaceProjectsButton.setSelection(configuration.isResolveWorkspaceProjects()); // includeModulesButton.setSelection(configuration.shouldIncludeModules()); selectedProfilesText.setText(configuration.getSelectedProfiles()); + IVMInstall vmInstall = configuration.getMavenJre(); + if(vmInstall != null) { + alternateJREsRadioButton.setSelection(true); + alternateJREsCombo.select(vmInstalls.indexOf(vmInstall)); + alternateJREsCombo.setEnabled(true); + } else { + defaultJRERadioButton.setSelection(true); + alternateJREsCombo.setEnabled(false); + } } @Override @@ -130,14 +323,15 @@ public boolean performOk() { final ResolverConfiguration configuration = new ResolverConfiguration(getResolverConfiguration()); if(configuration.getSelectedProfiles().equals(selectedProfilesText.getText()) && // configuration.shouldIncludeModules()==includeModulesButton.getSelection() && - configuration.isResolveWorkspaceProjects() == resolveWorspaceProjectsButton.getSelection()) { + configuration.isResolveWorkspaceProjects() == resolveWorspaceProjectsButton.getSelection() + && Objects.equals(configuration.getMavenJre(), getJRE())) { return true; } configuration.setResolveWorkspaceProjects(resolveWorspaceProjectsButton.getSelection()); // configuration.setIncludeModules(includeModulesButton.getSelection()); configuration.setSelectedProfiles(selectedProfilesText.getText()); - + configuration.setMavenJRE(getJRE()); IProjectConfigurationManager projectManager = MavenPlugin.getProjectConfigurationManager(); boolean isSet = projectManager.setResolverConfiguration(getProject(), configuration); if(isSet) { diff --git a/org.eclipse.m2e.core/META-INF/MANIFEST.MF b/org.eclipse.m2e.core/META-INF/MANIFEST.MF index c6ee25430a..9aeec7a693 100644 --- a/org.eclipse.m2e.core/META-INF/MANIFEST.MF +++ b/org.eclipse.m2e.core/META-INF/MANIFEST.MF @@ -53,12 +53,15 @@ MavenArtifact-ArtifactId: org.eclipse.m2e.core Import-Package: com.google.common.base;version="30.1.0", com.google.common.cache, javax.inject;version="1.0.0", + org.eclipse.jdt.core, + org.eclipse.jdt.launching, org.slf4j;version="[1.7.0,3.0.0)", org.slf4j.spi;version="[1.7.31,3.0.0)", com.google.gson;version="[2.9.1,3.0.0)", org.apache.commons.codec.digest;version="[1.14.0,2.0.0)" Automatic-Module-Name: org.eclipse.m2e.core Service-Component: OSGI-INF/org.eclipse.m2e.core.embedder.MavenModelManager.xml, + OSGI-INF/org.eclipse.m2e.core.internal.InstalledJREListingService.xml, OSGI-INF/org.eclipse.m2e.core.internal.embedder.EclipseLoggerManager.xml, OSGI-INF/org.eclipse.m2e.core.internal.embedder.MavenImpl.xml, OSGI-INF/org.eclipse.m2e.core.internal.embedder.PlexusContainerManager.xml, diff --git a/org.eclipse.m2e.core/src/org/eclipse/m2e/core/internal/IInstalledJREListingService.java b/org.eclipse.m2e.core/src/org/eclipse/m2e/core/internal/IInstalledJREListingService.java new file mode 100644 index 0000000000..062e05e497 --- /dev/null +++ b/org.eclipse.m2e.core/src/org/eclipse/m2e/core/internal/IInstalledJREListingService.java @@ -0,0 +1,38 @@ +/******************************************************************************** + * Copyright (c) 2022, 2022 Konrad Windszus 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: + * konradwindszus - initial API and implementation + ********************************************************************************/ + +package org.eclipse.m2e.core.internal; + +import java.util.Collection; +import java.util.Optional; + +import org.eclipse.core.resources.IProject; +import org.eclipse.jdt.launching.IVMInstall; + + +public interface IInstalledJREListingService { + + /** + * Returns all JREs known to Eclipse + * + * @return all JREs being known to Eclipse and usable with JDT Launching + */ + Collection getAllInstalledJREs(); + + /** + * @param project + * @return + */ + Optional getProjectBuildJRE(IProject project); + +} diff --git a/org.eclipse.m2e.core/src/org/eclipse/m2e/core/internal/InstalledJREListingService.java b/org.eclipse.m2e.core/src/org/eclipse/m2e/core/internal/InstalledJREListingService.java new file mode 100644 index 0000000000..ebcbaeb497 --- /dev/null +++ b/org.eclipse.m2e.core/src/org/eclipse/m2e/core/internal/InstalledJREListingService.java @@ -0,0 +1,79 @@ +/******************************************************************************** + * Copyright (c) 2022, 2022 Konrad Windszus 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: + * Konrad Windszus - initial API and implementation + ********************************************************************************/ + +package org.eclipse.m2e.core.internal; + +import java.util.Collection; +import java.util.LinkedList; +import java.util.Optional; + +import org.osgi.service.component.annotations.Component; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.eclipse.core.resources.IProject; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.JavaCore; +import org.eclipse.jdt.launching.IVMInstall; +import org.eclipse.jdt.launching.IVMInstall2; +import org.eclipse.jdt.launching.IVMInstallType; +import org.eclipse.jdt.launching.JavaRuntime; + + +/** + * Lists all installed JREs available. + */ +@Component +public class InstalledJREListingService implements IInstalledJREListingService { + + private static final Logger log = LoggerFactory.getLogger(InstalledJREListingService.class); + + @Override + public Collection getAllInstalledJREs() { + // find all matching JVMs and sort by their version + Collection installedJREs = new LinkedList<>(); + for(IVMInstallType vmInstallType : JavaRuntime.getVMInstallTypes()) { + for(IVMInstall vmInstall : vmInstallType.getVMInstalls()) { + if(vmInstall instanceof IVMInstall2) { + // TODO: fix cast + installedJREs.add(vmInstall); + } else { + log.debug("Skipping IVMInstall '{}' from type {} as not implementing IVMInstall2", vmInstall.getName(), + vmInstallType.getName()); + } + } + } + return installedJREs; + } + + @Override + public Optional getProjectBuildJRE(IProject project) { + IVMInstall vmInstall = null; + if(project != null && project.exists()) { + try { + IJavaProject javaProject = JavaCore.create(project); + if(javaProject.exists()) { + vmInstall = JavaRuntime.getVMInstall(javaProject); + } + } catch(CoreException ex) { + throw new IllegalStateException("Could not retrieve build JRE for given project", ex); + } + } + if(vmInstall == null) { + vmInstall = JavaRuntime.getDefaultVMInstall(); + } + return Optional.ofNullable(vmInstall); + } + +} diff --git a/org.eclipse.m2e.core/src/org/eclipse/m2e/core/internal/project/ProjectConfigurationManager.java b/org.eclipse.m2e.core/src/org/eclipse/m2e/core/internal/project/ProjectConfigurationManager.java index 21ad76747c..30d071f8ef 100644 --- a/org.eclipse.m2e.core/src/org/eclipse/m2e/core/internal/project/ProjectConfigurationManager.java +++ b/org.eclipse.m2e.core/src/org/eclipse/m2e/core/internal/project/ProjectConfigurationManager.java @@ -26,8 +26,12 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Map.Entry; +import java.util.NavigableMap; +import java.util.Optional; import java.util.Properties; import java.util.Set; +import java.util.TreeMap; import java.util.regex.Matcher; import org.osgi.service.component.annotations.Component; @@ -57,10 +61,20 @@ import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.SubMonitor; import org.eclipse.core.runtime.jobs.ISchedulingRule; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.JavaCore; +import org.eclipse.jdt.launching.IVMInstall; +import org.eclipse.jdt.launching.IVMInstall2; +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.model.Model; import org.apache.maven.model.Parent; +import org.apache.maven.plugin.MojoExecution; import org.apache.maven.project.MavenProject; import org.eclipse.m2e.core.MavenPlugin; @@ -68,6 +82,7 @@ import org.eclipse.m2e.core.embedder.IMavenConfiguration; import org.eclipse.m2e.core.embedder.IMavenExecutionContext; import org.eclipse.m2e.core.embedder.MavenModelManager; +import org.eclipse.m2e.core.internal.IInstalledJREListingService; import org.eclipse.m2e.core.internal.IMavenConstants; import org.eclipse.m2e.core.internal.IMavenToolbox; import org.eclipse.m2e.core.internal.Messages; @@ -124,6 +139,15 @@ public class ProjectConfigurationManager @Reference PlexusContainerManager containerManager; + @Reference + IInstalledJREListingService installedJREListingService; + + 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$ + @Override public List importProjects(Collection projectInfos, ProjectImportConfiguration configuration, IProgressMonitor monitor) throws CoreException { @@ -500,9 +524,144 @@ private void updateProjectConfiguration(ProjectConfigurationRequest request, IPr return null; }, monitor); + // must happen after calling the project configurators (as it checks the set Java project JRE) + configureMavenJre(mavenProjectFacade, monitor); log.debug("Updated project configuration for {} in {} ms.", mavenProjectFacade, System.currentTimeMillis() - start); //$NON-NLS-1$ } + /** + * Configure Maven JRE Library based on maven-enforcer-plugin + * @param configuration + */ + private void configureMavenJre(IMavenProjectFacade facade, IProgressMonitor monitor) { + IVMInstall configuredVm = facade.getConfiguration().getMavenJre(); + if (configuredVm == null) { + configuredVm = getDefaultVm(facade.getProject()); + } + IVMInstall vm = getBestMatchingVmInstallFromEnforcerRule(facade, monitor, configuredVm); + if(vm != null) { + ResolverConfiguration resolverConfiguration = new ResolverConfiguration(facade.getConfiguration()); + resolverConfiguration.setMavenJRE(vm); + ResolverConfigurationIO.saveResolverConfiguration(facade.getProject(), resolverConfiguration); + } + } + + /** + * @param project the Eclipse project + * @return the default Java VM or the workspace default as fallback + */ + private IVMInstall getDefaultVm(IProject project) { + // populate also in case of defaults to check if a change is needed + IJavaProject javaProject = JavaCore.create(project); + IVMInstall vm = null; + if (javaProject.exists()) { + try { + vm = JavaRuntime.getVMInstall(javaProject); + } catch(CoreException ex) { + log.warn("Cannot determine Java project's JRE", ex); + } + } + if (vm == null) { + vm = JavaRuntime.getDefaultVMInstall(); + } + return vm; + } + + private List getEnforcerMojoExecutions(IMavenProjectFacade facade, IProgressMonitor monitor) + throws CoreException { + return facade.getMojoExecutions(ENFORCER_PLUGIN_GROUP_ID, ENFORCER_PLUGIN_ARTIFACT_ID, monitor, GOAL_ENFORCE); + } + + private IVMInstall getBestMatchingVmInstallFromEnforcerRule(IMavenProjectFacade facade, IProgressMonitor monitor, + IVMInstall configuredJvm) { + try { + List mojoExecutions = getEnforcerMojoExecutions(facade, monitor); + for(MojoExecution mojoExecution : mojoExecutions) { + IVMInstall vmInstall = getBestMatchingVmInstall(facade.getProject(), facade.getMavenProject(), configuredJvm, + mojoExecution, + monitor); + if(vmInstall != null) { + return vmInstall; + } + } + } catch(CoreException | InvalidVersionSpecificationException ex) { + log.error("Failed to determine minimum build Java version, assuming default", ex); + } + return null; + } + + private IVMInstall getBestMatchingVmInstall(IProject project, MavenProject mavenProject, IVMInstall configuredJvm, + 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; + } + // normalize version (https://issues.apache.org/jira/browse/MENFORCER-440) + if("8".equals(version)) { + version = "1.8"; + } + return getBestMatchingVmInstall(version, project, configuredJvm); + } + + private IVMInstall getBestMatchingVmInstall(String versionSpec, IProject project, IVMInstall configuredJvm) + throws InvalidVersionSpecificationException { + VersionRange vr = VersionRange.createFromVersionSpec(versionSpec); + if(configuredJvm != null && getVmVersionMatchingVersionRequirement(vr, configuredJvm) != null) { + return configuredJvm; + } + // is it satisfied by default JRE of the current project? + Optional projectBuildJRE = installedJREListingService.getProjectBuildJRE(project); + if(projectBuildJRE.isPresent() && getVmVersionMatchingVersionRequirement(vr, projectBuildJRE.get()) != null) { + return null; + } + + // find all matching JVMs and sort by their version + NavigableMap vmByJavaVersion = new TreeMap<>(); + for(IVMInstall jre : installedJREListingService.getAllInstalledJREs()) { + ArtifactVersion vmVersion = getVmVersionMatchingVersionRequirement(vr, jre); + if(vmVersion != null) { + vmByJavaVersion.put(vmVersion, jre); + } + } + ArtifactVersion mainVersion; + // for ranges with only lower bound or just a recommended version pick newest version having equal major version + if(vr.getRestrictions().size() == 1 && vr.getRestrictions().get(0).getUpperBound() == null) { + mainVersion = vr.getRestrictions().get(0).getLowerBound(); + } else if(vr.getRecommendedVersion()!=null) { + mainVersion = vr.getRecommendedVersion(); + } else { + mainVersion = null; + } + if (mainVersion != null) { + return vmByJavaVersion.descendingMap().entrySet().stream() + .filter(e -> e.getKey().getMajorVersion() == mainVersion.getMajorVersion()).map(Entry::getValue).findFirst() + .orElse(null); + } + return vmByJavaVersion.lastEntry()!=null?vmByJavaVersion.lastEntry().getValue():null; + } + + /** + * @param vr the version requirement, recommended version is interpreted as lower bound + * @param vmInstall the VM to check against + * @return the java version of the vm if the given VM matches the version requirement, {@code null} otherwise + */ + private ArtifactVersion getVmVersionMatchingVersionRequirement(VersionRange vr, IVMInstall vmInstall) { + if(vmInstall instanceof IVMInstall2 vmInstall2 && vmInstall2.getJavaVersion() != null) { + ArtifactVersion vmVersion = new DefaultArtifactVersion(vmInstall2.getJavaVersion()); + if(vr.getRecommendedVersion() != null) { + if(vmVersion.compareTo(vr.getRecommendedVersion()) >= 0) { + return vmVersion; + } + } else if(vr.containsVersion(vmVersion)) { + return vmVersion; + } + } + return null; + } + @Override public void enableMavenNature(IProject project, IProjectConfiguration configuration, IProgressMonitor monitor) throws CoreException { diff --git a/org.eclipse.m2e.core/src/org/eclipse/m2e/core/internal/project/ResolverConfigurationIO.java b/org.eclipse.m2e.core/src/org/eclipse/m2e/core/internal/project/ResolverConfigurationIO.java index 5354c29fe6..5711215a6a 100644 --- a/org.eclipse.m2e.core/src/org/eclipse/m2e/core/internal/project/ResolverConfigurationIO.java +++ b/org.eclipse.m2e.core/src/org/eclipse/m2e/core/internal/project/ResolverConfigurationIO.java @@ -31,6 +31,8 @@ import org.eclipse.core.resources.ProjectScope; import org.eclipse.core.runtime.preferences.IEclipsePreferences; import org.eclipse.core.runtime.preferences.IScopeContext; +import org.eclipse.jdt.launching.IVMInstall; +import org.eclipse.jdt.launching.JavaRuntime; import org.eclipse.m2e.core.internal.IMavenConstants; import org.eclipse.m2e.core.project.IProjectConfiguration; @@ -69,6 +71,8 @@ public class ResolverConfigurationIO { private static final String P_BASEDIR = "basedir"; + private static final String P_JRE_COMPOSITE_ID = "jreCompositeId"; + private static final String PROPERTIES_KV_SEPARATOR = ">"; private static final String PROPERTIES_SEPARATOR = "|"; @@ -108,6 +112,12 @@ public static boolean saveResolverConfiguration(IProject project, IProjectConfig projectNode.remove(P_PROPERTIES); } + IVMInstall jreInstall = configuration.getMavenJre(); + if(jreInstall != null) { + projectNode.put(P_JRE_COMPOSITE_ID, JavaRuntime.getCompositeIdFromVM(jreInstall)); + } else { + projectNode.remove(P_JRE_COMPOSITE_ID); + } try { projectNode.flush(); return true; @@ -142,6 +152,7 @@ public static IProjectConfiguration readResolverConfiguration(IProject project) configuration.setMultiModuleProjectDirectory(directory); } } + configuration.setMavenJRE(projectNode.get(P_JRE_COMPOSITE_ID, null)); return configuration; } diff --git a/org.eclipse.m2e.core/src/org/eclipse/m2e/core/internal/project/registry/MavenProjectFacade.java b/org.eclipse.m2e.core/src/org/eclipse/m2e/core/internal/project/registry/MavenProjectFacade.java index b0b334e1e4..990e33eab3 100644 --- a/org.eclipse.m2e.core/src/org/eclipse/m2e/core/internal/project/registry/MavenProjectFacade.java +++ b/org.eclipse.m2e.core/src/org/eclipse/m2e/core/internal/project/registry/MavenProjectFacade.java @@ -34,6 +34,8 @@ import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.Status; +import org.eclipse.jdt.launching.IVMInstall; +import org.eclipse.jdt.launching.JavaRuntime; import org.eclipse.osgi.util.NLS; import org.codehaus.plexus.util.xml.Xpp3Dom; @@ -649,6 +651,8 @@ private static final class MavenProjectConfiguration implements IProjectConfigur private final String profiles; + private final String jreCompositeId; + public MavenProjectConfiguration(IProjectConfiguration baseConfiguration, File multiModuleProjectDirectory) { if(baseConfiguration == null) { //we should really forbid this but some test seem to pass null! @@ -659,6 +663,7 @@ public MavenProjectConfiguration(IProjectConfiguration baseConfiguration, File m this.properties = Map.copyOf(baseConfiguration.getConfigurationProperties()); this.resolveWorkspace = baseConfiguration.isResolveWorkspaceProjects(); this.profiles = baseConfiguration.getSelectedProfiles(); + this.jreCompositeId = JavaRuntime.getCompositeIdFromVM(baseConfiguration.getMavenJre()); } @Override @@ -686,9 +691,15 @@ public File getMultiModuleProjectDirectory() { return multiModuleProjectDirectory; } + @Override + public IVMInstall getMavenJre() { + return JavaRuntime.getVMFromCompositeId(jreCompositeId); + } + @Override public int hashCode() { - return Objects.hash(mappingId, multiModuleProjectDirectory, profiles, properties, resolveWorkspace); + return Objects.hash(mappingId, multiModuleProjectDirectory, profiles, properties, resolveWorkspace, + jreCompositeId); } @Override @@ -706,7 +717,8 @@ public boolean equals(Object obj) { return Objects.equals(this.mappingId, other.mappingId) && Objects.equals(this.multiModuleProjectDirectory, other.multiModuleProjectDirectory) && Objects.equals(this.profiles, other.profiles) && Objects.equals(this.properties, other.properties) - && this.resolveWorkspace == other.resolveWorkspace; + && this.resolveWorkspace == other.resolveWorkspace + && Objects.equals(this.jreCompositeId, other.jreCompositeId); } } diff --git a/org.eclipse.m2e.core/src/org/eclipse/m2e/core/project/IProjectConfiguration.java b/org.eclipse.m2e.core/src/org/eclipse/m2e/core/project/IProjectConfiguration.java index 4fa9765200..04e55a7b8b 100644 --- a/org.eclipse.m2e.core/src/org/eclipse/m2e/core/project/IProjectConfiguration.java +++ b/org.eclipse.m2e.core/src/org/eclipse/m2e/core/project/IProjectConfiguration.java @@ -16,6 +16,8 @@ import java.util.List; import java.util.Map; +import org.eclipse.jdt.launching.IVMInstall; + /** * {@link IProjectConfiguration} represents the project specific configuration, many projects can share the same @@ -62,4 +64,9 @@ private static List parseProfiles(String profilesAsText, boolean status) return profiles; } + /** + * Gets the JRE to use for executing Maven If {@code null} is returned the project's build JRE or (if not available) + * the workspace default JRE should be used + */ + IVMInstall getMavenJre(); } diff --git a/org.eclipse.m2e.core/src/org/eclipse/m2e/core/project/ResolverConfiguration.java b/org.eclipse.m2e.core/src/org/eclipse/m2e/core/project/ResolverConfiguration.java index cdea963a42..fecb193ea3 100644 --- a/org.eclipse.m2e.core/src/org/eclipse/m2e/core/project/ResolverConfiguration.java +++ b/org.eclipse.m2e.core/src/org/eclipse/m2e/core/project/ResolverConfiguration.java @@ -24,6 +24,8 @@ import java.util.Set; import org.eclipse.core.resources.IProject; +import org.eclipse.jdt.launching.IVMInstall; +import org.eclipse.jdt.launching.JavaRuntime; import org.eclipse.m2e.core.internal.embedder.PlexusContainerManager; @@ -47,6 +49,8 @@ public class ResolverConfiguration implements Serializable, IProjectConfiguratio private File multiModuleProjectDirectory; + private String jreCompositeId; + public ResolverConfiguration() { } @@ -66,6 +70,7 @@ public ResolverConfiguration(IProjectConfiguration resolverConfiguration) { setProperties(properties2); setResolveWorkspaceProjects(resolverConfiguration.isResolveWorkspaceProjects()); setSelectedProfiles(resolverConfiguration.getSelectedProfiles()); + jreCompositeId = JavaRuntime.getCompositeIdFromVM(resolverConfiguration.getMavenJre()); } /* (non-Javadoc) @@ -182,4 +187,26 @@ public File getMultiModuleProjectDirectory() { public void setMultiModuleProjectDirectory(File multiModuleProjectDirectory) { this.multiModuleProjectDirectory = multiModuleProjectDirectory; } + + /** + * @param vmInstall the alternate JRE to use for executing Maven. If {@code null} is passed the project's build JRE + * should be used + */ + public void setMavenJRE(IVMInstall vmInstall) { + jreCompositeId = JavaRuntime.getCompositeIdFromVM(vmInstall); + } + + /** + * @param jreCompositeId the composite id of the alternate JRE to use for executing Maven. If {@code null} is passed + * the project's build JRE should be used + */ + public void setMavenJRE(String jreCompositeId) { + this.jreCompositeId = jreCompositeId; + } + + @Override + public IVMInstall getMavenJre() { + return JavaRuntime.getVMFromCompositeId(jreCompositeId); + } + } 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/src/org/eclipse/m2e/jdt/internal/AbstractJavaProjectConfigurator.java b/org.eclipse.m2e.jdt/src/org/eclipse/m2e/jdt/internal/AbstractJavaProjectConfigurator.java index 09a7654f00..f78f5f7ef9 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 @@ -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); @@ -794,12 +780,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); @@ -842,74 +822,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..1f3e93b331 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 @@ -216,11 +216,7 @@ 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()); - } - + workingCopy.setAttribute(IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME, basedir.getProject().getName()); // 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" @@ -372,6 +368,7 @@ public void dispose() { workingCopy.setAttribute(MavenLaunchConstants.ATTR_POM_DIR, LaunchingUtils.generateProjectLocationVariableExpression(basedir.getProject())); + workingCopy.setAttribute(IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME, basedir.getProject().getName()); setProjectConfiguration(workingCopy, basedir); // set other defaults if needed 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..8598c970d4 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 @@ -25,13 +25,20 @@ 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.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; 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.jdt.launching.IJavaLaunchConfigurationConstants; +import org.eclipse.jdt.launching.IVMInstall; import org.eclipse.jdt.launching.IVMRunner; import org.eclipse.jdt.launching.JavaLaunchDelegate; import org.eclipse.osgi.util.NLS; @@ -43,7 +50,11 @@ import org.eclipse.m2e.actions.MavenLaunchConstants; import org.eclipse.m2e.core.MavenPlugin; import org.eclipse.m2e.core.embedder.IMavenConfiguration; +import org.eclipse.m2e.core.internal.IMavenConstants; import org.eclipse.m2e.core.internal.launch.AbstractMavenRuntime; +import org.eclipse.m2e.core.project.IMavenProjectFacade; +import org.eclipse.m2e.core.project.IMavenProjectRegistry; +import org.eclipse.m2e.core.project.IProjectConfiguration; import org.eclipse.m2e.internal.launch.MavenRuntimeLaunchSupport.VMArguments; @@ -163,6 +174,36 @@ public String getProgramArguments(ILaunchConfiguration configuration) throws Cor return programArguments; } + public IVMInstall getVMInstall(ILaunchConfiguration configuration) throws CoreException { + // use the Maven JVM if nothing explicitly configured in the launch configuration + String jreAttr = configuration.getAttribute(IJavaLaunchConfigurationConstants.ATTR_JRE_CONTAINER_PATH, + (String) null); + if(jreAttr == null) { + String pomDir = LaunchingUtils.substituteVar(configuration.getAttribute(MavenLaunchConstants.ATTR_POM_DIR, "")); + // try to retrieve associated Eclipse project + IContainer pomContainer = ResourcesPlugin.getWorkspace().getRoot() + .getContainerForLocation(Path.fromOSString(pomDir)); + IProjectConfiguration mavenProjectConfiguration = getMavenProjectConfiguration(pomContainer); + if(mavenProjectConfiguration != null && mavenProjectConfiguration.getMavenJre() != null) { + return mavenProjectConfiguration.getMavenJre(); + } + } + return super.getVMInstall(configuration); + } + + private IProjectConfiguration getMavenProjectConfiguration(IContainer pomContainer) { + if(pomContainer == null) { + return null; + } + IMavenProjectRegistry projectManager = MavenPlugin.getMavenProjectRegistry(); + IFile pomFile = pomContainer.getFile(new Path(IMavenConstants.POM_FILE_NAME)); + IMavenProjectFacade projectFacade = projectManager.create(pomFile, true, new NullProgressMonitor()); + if(projectFacade != null) { + return projectFacade.getConfiguration(); + } + return null; + } + @Override public String getVMArguments(ILaunchConfiguration configuration) throws CoreException { VMArguments arguments = launchSupport.getVMArguments(); 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..8f5cec7632 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 @@ -13,15 +13,25 @@ package org.eclipse.m2e.ui.internal.launch; +import java.util.function.Supplier; + +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.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.swt.SWT; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.widgets.Composite; +import org.eclipse.m2e.core.internal.project.ResolverConfigurationIO; +import org.eclipse.m2e.core.project.IProjectConfiguration; + @SuppressWarnings("restriction") public class MavenJRETab extends JavaJRETab { @@ -40,6 +50,82 @@ public void createControl(Composite parent) { ((GridData) vmArgumentsBlock.getControl().getLayoutData()).horizontalSpan = 2; } + private IProjectConfiguration getResolverConfiguration() { + IJavaProject javaProject = getJavaProject(); + if(javaProject == null) { + return null; + } + return ResolverConfigurationIO.readResolverConfiguration(javaProject.getProject()); + } + + /** + * Retrieves information about Maven JRE set in Maven project properties. + * + * @return the descriptor for the default Maven JRE + */ + protected JREDescriptor getDefaultJREDescriptor() { + return new MavenJREDescriptor( + () -> getResolverConfiguration() != null ? getResolverConfiguration().getMavenJre() : null, + () -> super.getDefaultJREDescriptor().getDescription()); + } + + private static final class MavenJREDescriptor extends JREDescriptor { + + private final Supplier defaultDescription; + + private final Supplier mavenJreSupplier; + + MavenJREDescriptor(Supplier mavenJreSupplier, Supplier defaultDescription) { + this.mavenJreSupplier = mavenJreSupplier; + this.defaultDescription = defaultDescription; + + } + + /* (non-Javadoc) + * @see org.eclipse.jdt.internal.debug.ui.jres.DefaultJREDescriptor#getDescription() + */ + @Override + public String getDescription() { + // first check Maven JRE configuration + IVMInstall mavenJre = mavenJreSupplier.get(); + final String jreName; + if(mavenJre != null) { + // add link + jreName = mavenJreSupplier.get().getName(); + } else { + jreName = defaultDescription.get(); + } + // then fall back to default + return "Maven JRE: " + jreName; + } + }; + + /** + * Need to overwrite from parent, to prevent calling JavaJRETab.checkCompliance(). + */ + 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 + /** + * 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); diff --git a/org.eclipse.m2e.launching/src/org/eclipse/m2e/ui/internal/launch/MavenLaunchMainTab.java b/org.eclipse.m2e.launching/src/org/eclipse/m2e/ui/internal/launch/MavenLaunchMainTab.java index 3d1f40a46d..c3a25f29ed 100644 --- a/org.eclipse.m2e.launching/src/org/eclipse/m2e/ui/internal/launch/MavenLaunchMainTab.java +++ b/org.eclipse.m2e.launching/src/org/eclipse/m2e/ui/internal/launch/MavenLaunchMainTab.java @@ -22,15 +22,18 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.Path; import org.eclipse.core.variables.VariablesPlugin; import org.eclipse.debug.core.ILaunchConfiguration; import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy; import org.eclipse.debug.ui.AbstractLaunchConfigurationTab; import org.eclipse.debug.ui.StringVariableSelectionDialog; +import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants; import org.eclipse.jface.dialogs.IDialogConstants; import org.eclipse.jface.viewers.TableViewer; import org.eclipse.swt.SWT; @@ -549,6 +552,20 @@ public void setDefaults(ILaunchConfigurationWorkingCopy configuration) { public void performApply(ILaunchConfigurationWorkingCopy configuration) { configuration.setAttribute(ATTR_POM_DIR, this.pomDirNameText.getText()); + // set associated project name (if there is some) + try { + String pomDir = LaunchingUtils.substituteVar(this.pomDirNameText.getText()); + // try to retrieve associated Eclipse project + IContainer container = ResourcesPlugin.getWorkspace().getRoot() + .getContainerForLocation(Path.fromOSString(pomDir)); + if(container != null && container.getProject() != null) { + configuration.setAttribute(IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME, + container.getProject().getName()); + } + } catch(CoreException e) { + log.debug("Cannot substitute vars in {}", this.pomDirNameText.getText(), e); + } + configuration.setAttribute(ATTR_GOALS, this.goalsText.getText()); configuration.setAttribute(ATTR_PROFILES, this.profilesText.getText());