diff --git a/org.eclipse.m2e.launching/META-INF/MANIFEST.MF b/org.eclipse.m2e.launching/META-INF/MANIFEST.MF index 523d712280..b8ee041a76 100644 --- a/org.eclipse.m2e.launching/META-INF/MANIFEST.MF +++ b/org.eclipse.m2e.launching/META-INF/MANIFEST.MF @@ -18,7 +18,9 @@ Require-Bundle: org.eclipse.core.runtime;bundle-version="[3.27.0,4.0.0)", org.eclipse.m2e.maven.runtime;bundle-version="[3.8.6,4.0.0)", org.eclipse.m2e.core;bundle-version="[2.0.0,3.0.0)", org.eclipse.m2e.core.ui;bundle-version="[2.0.0,3.0.0)", - org.eclipse.m2e.workspace.cli;bundle-version="0.1.0" + org.eclipse.m2e.workspace.cli;bundle-version="0.1.0", + org.eclipse.jdt.junit.core, + org.eclipse.unittest.ui;bundle-version="1.1.200" Bundle-ActivationPolicy: lazy Bundle-RequiredExecutionEnvironment: JavaSE-17 Bundle-Vendor: %Bundle-Vendor diff --git a/org.eclipse.m2e.launching/plugin.xml b/org.eclipse.m2e.launching/plugin.xml index fec1e03370..661e235d4b 100644 --- a/org.eclipse.m2e.launching/plugin.xml +++ b/org.eclipse.m2e.launching/plugin.xml @@ -243,5 +243,12 @@ class="org.eclipse.m2e.internal.launch.MavenConsoleLineTracker" processType="java"/> + + + + 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 69c2ef633f..b5819df36e 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 @@ -56,6 +56,7 @@ import org.eclipse.ui.IFileEditorInput; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.dialogs.ElementListSelectionDialog; +import org.eclipse.unittest.ui.ConfigureViewerSupport; import org.eclipse.m2e.core.MavenPlugin; import org.eclipse.m2e.core.embedder.IMavenExecutableLocation; @@ -75,6 +76,10 @@ * @author Eugene Kuleshov */ public class ExecutePomAction implements ILaunchShortcut, IExecutableExtension, ILaunchShortcut2 { + + public static final ConfigureViewerSupport TEST_RESULT_LISTENER_CONFIGURER = new ConfigureViewerSupport( + "org.eclipse.m2e.launching.testViewSupport"); + private static final Logger log = LoggerFactory.getLogger(ExecutePomAction.class); private boolean showDialog = false; @@ -212,7 +217,6 @@ private ILaunchConfiguration createLaunchConfiguration(IContainer basedir, Strin workingCopy.setAttribute(IDebugUIConstants.ATTR_PRIVATE, true); workingCopy.setAttribute(RefreshTab.ATTR_REFRESH_SCOPE, "${project}"); //$NON-NLS-1$ workingCopy.setAttribute(RefreshTab.ATTR_REFRESH_RECURSIVE, true); - setProjectConfiguration(workingCopy, basedir); // TODO when launching Maven with debugger consider to add the following property @@ -237,6 +241,7 @@ private void setProjectConfiguration(ILaunchConfigurationWorkingCopy workingCopy workingCopy.setAttribute(MavenLaunchConstants.ATTR_PROFILES, selectedProfiles); } } + TEST_RESULT_LISTENER_CONFIGURER.apply(workingCopy); } private ILaunchConfiguration getLaunchConfiguration(IContainer basedir, String mode) { diff --git a/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/MavenBuildConnectionProcess.java b/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/MavenBuildConnectionProcess.java new file mode 100644 index 0000000000..e9f724417d --- /dev/null +++ b/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/MavenBuildConnectionProcess.java @@ -0,0 +1,154 @@ +/******************************************************************************** + * Copyright (c) 2024 Christoph Läubrich and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Christoph Läubrich - initial API and implementation + ********************************************************************************/ + +package org.eclipse.m2e.internal.launch; + +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; + +import org.eclipse.core.runtime.Status; +import org.eclipse.debug.core.DebugException; +import org.eclipse.debug.core.ILaunch; +import org.eclipse.debug.core.model.IProcess; +import org.eclipse.debug.core.model.IStreamsProxy; + +import org.eclipse.m2e.core.embedder.ArtifactKey; +import org.eclipse.m2e.internal.maven.listener.M2EMavenBuildDataBridge; +import org.eclipse.m2e.internal.maven.listener.M2EMavenBuildDataBridge.MavenBuildConnection; +import org.eclipse.m2e.internal.maven.listener.MavenBuildListener; +import org.eclipse.m2e.internal.maven.listener.MavenProjectBuildData; +import org.eclipse.m2e.internal.maven.listener.MavenTestEvent; + + +/** + * A process that represents the remote connection to the maven process + */ +public class MavenBuildConnectionProcess implements IProcess { + + private ILaunch launch; + + private Map attributes = new HashMap<>(); + + private Map projects = new ConcurrentHashMap<>(); + + private MavenBuildConnection connection; + + private List buildListeners = new CopyOnWriteArrayList<>(); + + public MavenBuildConnectionProcess(ILaunch launch) { + this.launch = launch; + launch.addProcess(this); + attributes.put(IProcess.ATTR_PROCESS_TYPE, "m2e-build-endpoint"); + } + + public T getAdapter(Class adapter) { + return null; + } + + public boolean canTerminate() { + return true; + } + + public boolean isTerminated() { + return connection == null || connection.isReadCompleted(); + } + + public void terminate() throws DebugException { + if(connection != null) { + try { + connection.close(); + } catch(IOException ex) { + throw new DebugException(Status.error("Terminate failed", ex)); + } + connection = null; + } + } + + public String getLabel() { + return "M2E Build Listener"; + } + + public ILaunch getLaunch() { + return launch; + } + + public IStreamsProxy getStreamsProxy() { + return null; + } + + @Override + public void setAttribute(String key, String value) { + attributes.put(key, value); + } + + @Override + public String getAttribute(String key) { + return attributes.get(key); + } + + public void addMavenBuildListener(MavenBuildListener listener) { + buildListeners.add(listener); + } + + /** + * @param mavenTestRunnerClient + * @return + */ + public void removeMavenBuildListener(MavenBuildListener listener) { + buildListeners.remove(listener); + } + + @Override + public int getExitValue() { + return 0; + } + + /** + * @return the projects + */ + public Map getProjects() { + return this.projects; + } + + void connect() throws IOException { + connection = M2EMavenBuildDataBridge.prepareConnection(launch.getLaunchConfiguration().getName(), + new MavenBuildListener() { + + @Override + public void projectStarted(MavenProjectBuildData data) { + projects.put(new ArtifactKey(data.groupId, data.artifactId, data.version, null), data); + } + + @Override + public void onTestEvent(MavenTestEvent mavenTestEvent) { + for(MavenBuildListener mavenBuildListener : buildListeners) { + mavenBuildListener.onTestEvent(mavenTestEvent); + } + } + + public void close() { + buildListeners.clear(); + launch.removeProcess(MavenBuildConnectionProcess.this); + } + }); + } + + String getMavenVMArguments() throws IOException { + return connection.getMavenVMArguments(); + } + +} diff --git a/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/MavenBuildProjectDataConnection.java b/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/MavenBuildProjectDataConnection.java index cd9abfbcd0..3ae45480fc 100644 --- a/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/MavenBuildProjectDataConnection.java +++ b/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/MavenBuildProjectDataConnection.java @@ -16,50 +16,39 @@ import java.io.IOException; import java.util.Arrays; import java.util.Map; -import java.util.Objects; -import java.util.concurrent.ConcurrentHashMap; -import java.util.stream.Stream; +import java.util.Optional; import org.eclipse.core.runtime.CoreException; +import org.eclipse.debug.core.DebugException; import org.eclipse.debug.core.DebugPlugin; import org.eclipse.debug.core.ILaunch; import org.eclipse.debug.core.ILaunchesListener2; +import org.eclipse.debug.core.model.IProcess; import org.eclipse.m2e.core.embedder.ArtifactKey; import org.eclipse.m2e.core.internal.launch.MavenEmbeddedRuntime; import org.eclipse.m2e.internal.launch.MavenRuntimeLaunchSupport.VMArguments; -import org.eclipse.m2e.internal.maven.listener.M2EMavenBuildDataBridge; -import org.eclipse.m2e.internal.maven.listener.M2EMavenBuildDataBridge.MavenBuildConnection; -import org.eclipse.m2e.internal.maven.listener.M2EMavenBuildDataBridge.MavenProjectBuildData; +import org.eclipse.m2e.internal.maven.listener.MavenProjectBuildData; public class MavenBuildProjectDataConnection { - private static record MavenBuildConnectionData(Map projects, - MavenBuildConnection connection) { - } - - private static final Map LAUNCH_PROJECT_DATA = new ConcurrentHashMap<>(); - static { DebugPlugin.getDefault().getLaunchManager().addLaunchListener(new ILaunchesListener2() { public void launchesRemoved(ILaunch[] launches) { - closeServers(Arrays.stream(launches).map(LAUNCH_PROJECT_DATA::remove)); - } - - public void launchesTerminated(ILaunch[] launches) { - closeServers(Arrays.stream(launches).map(LAUNCH_PROJECT_DATA::get)); - } - - private static void closeServers(Stream connectionData) { - connectionData.filter(Objects::nonNull).forEach(c -> { + Arrays.stream(launches).flatMap(launch -> getConnection(launch).stream()).forEach(con -> { try { - c.connection().close(); - } catch(IOException ex) { // ignore + con.terminate(); + } catch(DebugException ex) { + //ignore } }); } + public void launchesTerminated(ILaunch[] launches) { + launchesRemoved(launches); + } + public void launchesAdded(ILaunch[] launches) { // ignore } @@ -71,33 +60,42 @@ public void launchesChanged(ILaunch[] launches) { // ignore static void openListenerConnection(ILaunch launch, VMArguments arguments) { try { if(MavenLaunchUtils.getMavenRuntime(launch.getLaunchConfiguration()) instanceof MavenEmbeddedRuntime) { - - Map projects = new ConcurrentHashMap<>(); - - MavenBuildConnection connection = M2EMavenBuildDataBridge.prepareConnection( - launch.getLaunchConfiguration().getName(), - d -> projects.put(new ArtifactKey(d.groupId, d.artifactId, d.version, null), d)); - - if(LAUNCH_PROJECT_DATA.putIfAbsent(launch, new MavenBuildConnectionData(projects, connection)) != null) { - connection.close(); + getConnection(launch).ifPresent(existing -> { + try { + existing.terminate(); + } catch(DebugException ex) { + } throw new IllegalStateException( "Maven bridge already created for launch of" + launch.getLaunchConfiguration().getName()); - } - arguments.append(connection.getMavenVMArguments()); + }); + MavenBuildConnectionProcess process = new MavenBuildConnectionProcess(launch); + process.connect(); + arguments.append(process.getMavenVMArguments()); } } catch(CoreException | IOException ex) { // ignore } } + public static Optional getConnection(ILaunch launch) { + for(IProcess process : launch.getProcesses()) { + if(process instanceof MavenBuildConnectionProcess connection) { + return Optional.of(connection); + } + } + return Optional.empty(); + } + static MavenProjectBuildData getBuildProject(ILaunch launch, String groupId, String artifactId, String version) { - MavenBuildConnectionData build = LAUNCH_PROJECT_DATA.get(launch); - if(build == null) { + Optional connection = getConnection(launch); + if(connection.isEmpty()) { return null; } + MavenBuildConnectionProcess process = connection.get(); + Map projects = process.getProjects(); ArtifactKey key = new ArtifactKey(groupId, artifactId, version, null); while(true) { - MavenProjectBuildData buildProject = build.projects().get(key); - if(buildProject != null || build.connection().isReadCompleted()) { + MavenProjectBuildData buildProject = projects.get(key); + if(buildProject != null || process.isTerminated()) { return buildProject; } Thread.onSpinWait(); // Await completion of project data read. It has to become available soon, since its GAV was printed on the console diff --git a/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/MavenConsoleLineTracker.java b/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/MavenConsoleLineTracker.java index 923df503c7..cca16682eb 100644 --- a/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/MavenConsoleLineTracker.java +++ b/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/MavenConsoleLineTracker.java @@ -65,7 +65,7 @@ import org.eclipse.m2e.core.internal.IMavenConstants; import org.eclipse.m2e.core.project.IBuildProjectFileResolver; -import org.eclipse.m2e.internal.maven.listener.M2EMavenBuildDataBridge.MavenProjectBuildData; +import org.eclipse.m2e.internal.maven.listener.MavenProjectBuildData; /** 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 3fad7104e1..1b81a02493 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 @@ -26,6 +26,8 @@ import java.util.SortedMap; import java.util.TreeMap; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -121,6 +123,15 @@ public void launch(ILaunchConfiguration configuration, String mode, ILaunch laun this.programArguments = null; try { + Bundle bundle = Platform.getBundle("org.eclipse.unittest.ui"); + if(bundle != null) { + try { + bundle.start(); + } catch(BundleException ex) { + //we tried our best... + } + } + System.out.println(bundle); this.launchSupport = MavenRuntimeLaunchSupport.create(configuration, monitor); this.extensionsSupport = MavenLaunchExtensionsSupport.create(configuration, launch); this.preferencesService = Platform.getPreferencesService(); @@ -129,7 +140,6 @@ public void launch(ILaunchConfiguration configuration, String mode, ILaunch laun log.info(" mvn {}", getProgramArguments(configuration)); //$NON-NLS-1$ extensionsSupport.configureSourceLookup(configuration, launch, monitor); - super.launch(configuration, mode, launch, monitor); } finally { this.launch = null; diff --git a/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/testing/MavenTestRunnerClient.java b/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/testing/MavenTestRunnerClient.java new file mode 100644 index 0000000000..e7675dd6ab --- /dev/null +++ b/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/testing/MavenTestRunnerClient.java @@ -0,0 +1,81 @@ +/******************************************************************************** + * Copyright (c) 2024 Christoph Läubrich and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Christoph Läubrich - initial API and implementation + ********************************************************************************/ + +package org.eclipse.m2e.internal.launch.testing; + +import org.eclipse.debug.core.ILaunch; +import org.eclipse.unittest.launcher.ITestRunnerClient; +import org.eclipse.unittest.model.ITestRunSession; + +import org.eclipse.m2e.internal.launch.MavenBuildProjectDataConnection; +import org.eclipse.m2e.internal.maven.listener.MavenBuildListener; +import org.eclipse.m2e.internal.maven.listener.MavenProjectBuildData; +import org.eclipse.m2e.internal.maven.listener.MavenTestEvent; + + +/** + * + */ +public class MavenTestRunnerClient implements ITestRunnerClient, MavenBuildListener { + + private ITestRunSession session; + + /** + * @param session + */ + public MavenTestRunnerClient(ITestRunSession session) { + this.session = session; + } + + public void startMonitoring() { + MavenBuildProjectDataConnection.getConnection(session.getLaunch()) + .ifPresent(con -> con.addMavenBuildListener(this)); + + } + + public void stopTest() { + stopMonitoring(); + ILaunch launch = session.getLaunch(); + try { + launch.terminate(); + } catch(Exception ex) { + } + } + + public void stopMonitoring() { + MavenBuildProjectDataConnection.getConnection(session.getLaunch()) + .ifPresent(con -> con.removeMavenBuildListener(this)); + } + + /** + * @return the session + */ + public ITestRunSession getSession() { + return this.session; + } + + public void projectStarted(MavenProjectBuildData data) { + // not interesting for us... + + } + + public void onTestEvent(MavenTestEvent mavenTestEvent) { + System.out.println(mavenTestEvent); + } + + public void close() { + // nothing to do yet... + System.out.println("CLOSED"); + } + +} diff --git a/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/testing/MavenTestViewSupport.java b/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/testing/MavenTestViewSupport.java new file mode 100644 index 0000000000..ae3c2c4928 --- /dev/null +++ b/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/testing/MavenTestViewSupport.java @@ -0,0 +1,65 @@ +/******************************************************************************** + * Copyright (c) 2024 Christoph Läubrich and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Christoph Läubrich - initial API and implementation + ********************************************************************************/ + +package org.eclipse.m2e.internal.launch.testing; + +import java.util.Collection; +import java.util.List; + +import org.eclipse.core.text.StringMatcher; +import org.eclipse.debug.core.ILaunchConfiguration; +import org.eclipse.jface.action.IAction; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.unittest.launcher.ITestRunnerClient; +import org.eclipse.unittest.model.ITestCaseElement; +import org.eclipse.unittest.model.ITestElement; +import org.eclipse.unittest.model.ITestRunSession; +import org.eclipse.unittest.model.ITestSuiteElement; +import org.eclipse.unittest.ui.ITestViewSupport; + + +public class MavenTestViewSupport implements ITestViewSupport { + + public ITestRunnerClient newTestRunnerClient(ITestRunSession session) { + return new MavenTestRunnerClient(session); + } + + public Collection getTraceExclusionFilterPatterns() { + return null; + } + + public IAction getOpenTestAction(Shell shell, ITestCaseElement testCase) { + return null; + } + + public IAction getOpenTestAction(Shell shell, ITestSuiteElement testSuite) { + return null; + } + + public IAction createOpenEditorAction(Shell shell, ITestElement failure, String traceLine) { + return null; + } + + public Runnable createShowStackTraceInConsoleViewActionDelegate(ITestElement failedTest) { + return null; + } + + public ILaunchConfiguration getRerunLaunchConfiguration(List testElements) { + return null; + } + + public String getDisplayName() { + return "Maven"; + } + +} diff --git a/org.eclipse.m2e.maven.runtime/pom.xml b/org.eclipse.m2e.maven.runtime/pom.xml index b1146a7ed1..15d8e92376 100644 --- a/org.eclipse.m2e.maven.runtime/pom.xml +++ b/org.eclipse.m2e.maven.runtime/pom.xml @@ -20,7 +20,7 @@ org.eclipse.m2e.maven.runtime - 3.9.600-SNAPSHOT + 3.9.601-SNAPSHOT jar M2E Embedded Maven Runtime (includes Incubating components) diff --git a/org.eclipse.m2e.maven.runtime/src/main/java/org/eclipse/m2e/internal/maven/listener/M2EMavenBuildDataBridge.java b/org.eclipse.m2e.maven.runtime/src/main/java/org/eclipse/m2e/internal/maven/listener/M2EMavenBuildDataBridge.java index 72d113fe06..13e0b27ff3 100644 --- a/org.eclipse.m2e.maven.runtime/src/main/java/org/eclipse/m2e/internal/maven/listener/M2EMavenBuildDataBridge.java +++ b/org.eclipse.m2e.maven.runtime/src/main/java/org/eclipse/m2e/internal/maven/listener/M2EMavenBuildDataBridge.java @@ -21,36 +21,26 @@ import java.nio.ByteBuffer; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; -import java.nio.file.Path; import java.nio.file.Paths; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.StringJoiner; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.Consumer; import javax.inject.Named; import javax.inject.Singleton; -import org.apache.maven.eventspy.EventSpy; -import org.apache.maven.execution.ExecutionEvent; +import org.apache.maven.AbstractMavenLifecycleParticipant; +import org.apache.maven.MavenExecutionException; import org.apache.maven.execution.ExecutionEvent.Type; -import org.apache.maven.project.MavenProject; +import org.apache.maven.execution.MavenSession; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * This {@link EventSpy} listens to certain events within a Maven build JVM and - * sends certain data (e.g. about projects being built) to the JVM of the - * Eclipse IDE that launched the Maven build JVM. - * - * @author Hannes Wellmann - * + * Bridge between the remote running maven and the local m2e to exchange event + * messages and information. */ -@Named +@Named("m2e") @Singleton -public class M2EMavenBuildDataBridge implements EventSpy { +public class M2EMavenBuildDataBridge extends AbstractMavenLifecycleParticipant { private static final String SOCKET_FILE_PROPERTY_NAME = "m2e.build.project.data.socket.port"; private static final String DATA_SET_SEPARATOR = ";;"; @@ -60,7 +50,7 @@ public class M2EMavenBuildDataBridge implements EventSpy { private SocketChannel writeChannel; @Override - public void init(Context context) throws IOException { + public void afterSessionStart(MavenSession session) throws MavenExecutionException { String socketPort = System.getProperty(SOCKET_FILE_PROPERTY_NAME); if (socketPort != null) { try { @@ -76,94 +66,51 @@ public void init(Context context) throws IOException { } @Override - public void close() throws IOException { - writeChannel.close(); + public void afterSessionEnd(MavenSession session) throws MavenExecutionException { + try { + writeChannel.close(); + } catch (IOException e) { + // we can't do much here anyways... + } } - @Override - public void onEvent(Object event) throws Exception { - if (writeChannel != null && event instanceof ExecutionEvent - && ((ExecutionEvent) event).getType() == Type.ProjectStarted) { - - String message = serializeProjectData(((ExecutionEvent) event).getProject()); + boolean isActive() { + return writeChannel != null; + } - ByteBuffer buffer = ByteBuffer.wrap(message.getBytes()); - synchronized (writeChannel) { + void sendMessage(String msg) { + SocketChannel channel = writeChannel; + if (channel != null) { + ByteBuffer buffer = ByteBuffer.wrap((msg + DATA_SET_SEPARATOR).getBytes()); + synchronized (channel) { while (buffer.hasRemaining()) { - writeChannel.write(buffer); + try { + channel.write(buffer); + } catch (IOException e) { + LOGGER.warn("Can't forward message to m2e: " + e); + } } } } } - private static String serializeProjectData(MavenProject project) { - StringJoiner data = new StringJoiner(","); - add(data, "groupId", project.getGroupId()); - add(data, "artifactId", project.getArtifactId()); - add(data, "version", project.getVersion()); - add(data, "file", project.getFile()); - add(data, "basedir", project.getBasedir()); - add(data, "build.directory", project.getBuild().getDirectory()); - return data.toString() + DATA_SET_SEPARATOR; - } - private static void add(StringJoiner data, String key, Object value) { - data.add(key + "=" + value); - } - /** - *

- * This method is supposed to be called from M2E within the Eclipse-IDE JVM. - *

- * - * @param dataSet the data-set to parse - * @return the {@link MavenProjectBuildData} parsed from the given string - */ - private static MavenProjectBuildData parseMavenBuildProject(String dataSet) { - Map data = new HashMap<>(8); - for (String entry : dataSet.split(",")) { - String[] keyValue = entry.split("="); - if (keyValue.length != 2) { - throw new IllegalStateException("Invalid data-set format" + dataSet); - } - data.put(keyValue[0], keyValue[1]); - } - return new MavenProjectBuildData(data); - } - public static final class MavenProjectBuildData { - public final String groupId; - public final String artifactId; - public final String version; - public final Path projectBasedir; - public final Path projectFile; - public final Path projectBuildDirectory; - - MavenProjectBuildData(Map data) { - if (data.size() != 6) { - throw new IllegalArgumentException(); - } - this.groupId = Objects.requireNonNull(data.get("groupId")); - this.artifactId = Objects.requireNonNull(data.get("artifactId")); - this.version = Objects.requireNonNull(data.get("version")); - this.projectBasedir = Paths.get(data.get("basedir")); - this.projectFile = Paths.get(data.get("file")); - this.projectBuildDirectory = Paths.get(data.get("build.directory")); - } - } + /** * Prepares the connection to a {@code Maven build JVM} to be launched and is * intended to be called from the Eclipse IDE JVM. * * @param label the label of the listener thread - * @param datasetListener the listener, which is notified whenever a new + * @param buildListener the listener, which is notified whenever a new * {@link MavenProjectBuildData MavenProjectBuildDataSet} * has arived from the Maven VM in the Eclipse-IDE VM. * @return the preapre {@link MavenBuildConnection} * @throws IOException */ - public static MavenBuildConnection prepareConnection(String label, Consumer datasetListener) + public static MavenBuildConnection prepareConnection(String label, MavenBuildListener buildListener) throws IOException { // TODO: use UNIX domain socket once Java-17 is required by Maven @@ -187,9 +134,21 @@ public static MavenBuildConnection prepareConnection(String label, Consumer= 0;) { String dataSet = message.substring(0, terminatorIndex); message.delete(0, terminatorIndex + DATA_SET_SEPARATOR.length()); - - MavenProjectBuildData buildData = parseMavenBuildProject(dataSet); - datasetListener.accept(buildData); + if (dataSet.startsWith(M2eEventSpy.PROJECT_START_EVENT)) { + MavenProjectBuildData buildData = MavenProjectBuildData + .parseMavenBuildProject( + dataSet.substring(M2eEventSpy.PROJECT_START_EVENT.length())); + buildListener.projectStarted(buildData); + } else if (dataSet.startsWith(M2eMojoExecutionListener.TEST_START_EVENT)) { + String path = dataSet.substring(M2eMojoExecutionListener.TEST_START_EVENT.length()); + buildListener.onTestEvent(new MavenTestEvent(Type.MojoStarted, Paths.get(path))); + } else if (dataSet.startsWith(M2eMojoExecutionListener.TEST_END_EVENT)) { + String path = dataSet.substring(M2eMojoExecutionListener.TEST_END_EVENT.length()); + buildListener.onTestEvent(new MavenTestEvent(Type.MojoSucceeded, Paths.get(path))); + } else if (dataSet.startsWith(M2eMojoExecutionListener.TEST_END_FAILED_EVENT)) { + String path = dataSet.substring(M2eMojoExecutionListener.TEST_END_EVENT.length()); + buildListener.onTestEvent(new MavenTestEvent(Type.MojoFailed, Paths.get(path))); + } } // Explicit cast for compatibility with covariant return type on JDK 9's // ByteBuffer @@ -198,6 +157,7 @@ public static MavenBuildConnection prepareConnection(String label, Consumer testExecutionPathMap = new ConcurrentHashMap<>(); + private BuildPluginManager buildPluginManager; + + @Inject + public M2eMojoExecutionListener(M2EMavenBuildDataBridge bridge, BuildPluginManager buildPluginManager) { + this.bridge = bridge; + this.buildPluginManager = buildPluginManager; + } + + @Override + public void beforeMojoExecution(MojoExecutionEvent event) throws MojoExecutionException { + if (!bridge.isActive()) { + return; + } + MojoExecution execution = event.getExecution(); + // it would be better if we have a common interface in plexus-build api that + // mojos can implement instead then we just need to query for the interface + boolean bndTest = isBndTest(execution); + if (isSurefireTest(execution) || isFailsafeTest(execution) || bndTest || isTychoSurefire(execution)) { + File reportsDirectory = getMojoParameterValue(execution, bndTest ? "reportsDir" : "reportsDirectory", + File.class, event.getSession()); + if (reportsDirectory != null) { + testExecutionPathMap.put(execution, reportsDirectory.toPath()); + bridge.sendMessage(TEST_START_EVENT + reportsDirectory.getAbsolutePath()); + } + } + } + + private static boolean isTychoSurefire(MojoExecution execution) { + return execution.getPlugin().getId().startsWith("org.eclipse.tycho:tycho-surefire-plugin:") + && ("test".equals(execution.getGoal()) || "plugin-test".equals(execution.getGoal()) + || "bnd-test".equals(execution.getGoal())); + + } + + private static boolean isFailsafeTest(MojoExecution execution) { + return execution.getPlugin().getId().startsWith("org.apache.maven.plugins:maven-failsafe-plugin:") + && "integration-test".equals(execution.getGoal()); + } + + private static boolean isSurefireTest(MojoExecution execution) { + return execution.getPlugin().getId().startsWith("org.apache.maven.plugins:maven-surefire-plugin:") + && "test".equals(execution.getGoal()); + } + + private static boolean isBndTest(MojoExecution execution) { + return execution.getPlugin().getId().startsWith("biz.aQute.bnd:bnd-testing-maven-plugin:") + && "testing".equals(execution.getGoal()); + } + + private void afterMojoExecution(MojoExecutionEvent event, String type) { + if (!bridge.isActive()) { + return; + } + Path testPath = testExecutionPathMap.remove(event.getExecution()); + if (testPath != null) { + bridge.sendMessage(type + testPath.toAbsolutePath().toString()); + } + } + + private T getMojoParameterValue(MojoExecution mojoExecution, String parameter, Class asType, + MavenSession session) { + Xpp3Dom dom = mojoExecution.getConfiguration(); + if (dom == null) { + return null; + } + PlexusConfiguration configuration = new XmlPlexusConfiguration(dom).getChild(parameter); + if (configuration == null) { + return null; + } + MojoDescriptor mojoDescriptor = mojoExecution.getMojoDescriptor(); + ClassRealm pluginRealm; + try { + pluginRealm = buildPluginManager.getPluginRealm(session, mojoDescriptor.getPluginDescriptor()); + ExpressionEvaluator expressionEvaluator = new PluginParameterExpressionEvaluator(session, mojoExecution); + ConfigurationConverter typeConverter = converterLookup.lookupConverterForType(asType); + + Object value = typeConverter.fromConfiguration(converterLookup, configuration, asType, + mojoDescriptor.getImplementationClass(), pluginRealm, expressionEvaluator, null); + return asType.cast(value); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + @Override + public void afterMojoExecutionSuccess(MojoExecutionEvent event) throws MojoExecutionException { + afterMojoExecution(event, TEST_END_EVENT); + } + + @Override + public void afterExecutionFailure(MojoExecutionEvent event) { + afterMojoExecution(event, TEST_END_FAILED_EVENT); + + } + +} diff --git a/org.eclipse.m2e.maven.runtime/src/main/java/org/eclipse/m2e/internal/maven/listener/MavenBuildListener.java b/org.eclipse.m2e.maven.runtime/src/main/java/org/eclipse/m2e/internal/maven/listener/MavenBuildListener.java new file mode 100644 index 0000000000..dce5b75ae9 --- /dev/null +++ b/org.eclipse.m2e.maven.runtime/src/main/java/org/eclipse/m2e/internal/maven/listener/MavenBuildListener.java @@ -0,0 +1,27 @@ +/******************************************************************************** + * Copyright (c) 2024 Christoph Läubrich and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Christoph Läubrich - initial API and implementation + ********************************************************************************/ +package org.eclipse.m2e.internal.maven.listener; + +/** + * Interface to implement when wanted to be informed about remote maven events + */ +public interface MavenBuildListener extends AutoCloseable { + + void projectStarted(MavenProjectBuildData data); + + void onTestEvent(MavenTestEvent mavenTestEvent); + + @Override + void close(); + +} diff --git a/org.eclipse.m2e.maven.runtime/src/main/java/org/eclipse/m2e/internal/maven/listener/MavenProjectBuildData.java b/org.eclipse.m2e.maven.runtime/src/main/java/org/eclipse/m2e/internal/maven/listener/MavenProjectBuildData.java new file mode 100644 index 0000000000..02b2bddecd --- /dev/null +++ b/org.eclipse.m2e.maven.runtime/src/main/java/org/eclipse/m2e/internal/maven/listener/MavenProjectBuildData.java @@ -0,0 +1,79 @@ +/******************************************************************************** + * Copyright (c) 2022, 2024 Hannes Wellmann 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: + * Hannes Wellmann - initial API and implementation + * Christoph Läubrich - factored out + ********************************************************************************/ +package org.eclipse.m2e.internal.maven.listener; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.StringJoiner; + +import org.apache.maven.project.MavenProject; + +public final class MavenProjectBuildData { + public final String groupId; + public final String artifactId; + public final String version; + public final Path projectBasedir; + public final Path projectFile; + public final Path projectBuildDirectory; + + MavenProjectBuildData(Map data) { + if (data.size() != 6) { + throw new IllegalArgumentException(); + } + this.groupId = Objects.requireNonNull(data.get("groupId")); + this.artifactId = Objects.requireNonNull(data.get("artifactId")); + this.version = Objects.requireNonNull(data.get("version")); + this.projectBasedir = Paths.get(data.get("basedir")); + this.projectFile = Paths.get(data.get("file")); + this.projectBuildDirectory = Paths.get(data.get("build.directory")); + } + + static String serializeProjectData(MavenProject project) { + StringJoiner data = new StringJoiner(","); + add(data, "groupId", project.getGroupId()); + add(data, "artifactId", project.getArtifactId()); + add(data, "version", project.getVersion()); + add(data, "file", project.getFile()); + add(data, "basedir", project.getBasedir()); + add(data, "build.directory", project.getBuild().getDirectory()); + return data.toString(); + } + + /** + *

+ * This method is supposed to be called from M2E within the Eclipse-IDE JVM. + *

+ * + * @param dataSet the data-set to parse + * @return the {@link MavenProjectBuildData} parsed from the given string + */ + static MavenProjectBuildData parseMavenBuildProject(String dataSet) { + Map data = new HashMap<>(8); + for (String entry : dataSet.split(",")) { + String[] keyValue = entry.split("="); + if (keyValue.length != 2) { + throw new IllegalStateException("Invalid data-set format" + dataSet); + } + data.put(keyValue[0], keyValue[1]); + } + return new MavenProjectBuildData(data); + } + + private static void add(StringJoiner data, String key, Object value) { + data.add(key + "=" + value); + } +} \ No newline at end of file diff --git a/org.eclipse.m2e.maven.runtime/src/main/java/org/eclipse/m2e/internal/maven/listener/MavenTestEvent.java b/org.eclipse.m2e.maven.runtime/src/main/java/org/eclipse/m2e/internal/maven/listener/MavenTestEvent.java new file mode 100644 index 0000000000..bf18b02295 --- /dev/null +++ b/org.eclipse.m2e.maven.runtime/src/main/java/org/eclipse/m2e/internal/maven/listener/MavenTestEvent.java @@ -0,0 +1,43 @@ +/******************************************************************************** + * Copyright (c) 2024 Christoph Läubrich and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Christoph Läubrich - initial API and implementation + ********************************************************************************/ +package org.eclipse.m2e.internal.maven.listener; + +import java.nio.file.Path; + +import org.apache.maven.execution.ExecutionEvent; + +public class MavenTestEvent { + + + private Path reportDirectory; + private ExecutionEvent.Type type; + + public MavenTestEvent(ExecutionEvent.Type type, Path reportDirectory) { + this.type = type; + this.reportDirectory = reportDirectory; + } + + public Path getReportDirectory() { + return reportDirectory; + } + + public ExecutionEvent.Type getType() { + return type; + } + + @Override + public String toString() { + return "MavenTestEvent [type=" + type + ", reportDirectory=" + reportDirectory + "]"; + } + +} diff --git a/pom.xml b/pom.xml index 4950bcd1b8..c39883b38e 100644 --- a/pom.xml +++ b/pom.xml @@ -103,7 +103,7 @@ org.eclipse.m2e org.eclipse.m2e.maven.runtime - 3.9.600-SNAPSHOT + 3.9.601-SNAPSHOT diff --git a/target-platform/target-platform.target b/target-platform/target-platform.target index f2730674ef..03656ce2b3 100644 --- a/target-platform/target-platform.target +++ b/target-platform/target-platform.target @@ -12,6 +12,7 @@ +